Scala 脚本没有 运行 Ubuntu

Scala script doesn't run on Ubuntu

我有一个以前工作的 Scala 脚本,当我尝试在新 PC 上 运行 它时,编译失败。

所以我做了一个简单的脚本来测试:

#!/bin/sh
exec scala -J-Xmx2g "[=12=]" "$@"
!#

println("test")

并尝试 运行 我得到:

test.scala 
error: Compile server encountered fatal condition: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
at scala.tools.nsc.io.SourceReader.read(SourceReader.scala:61)
at scala.tools.nsc.io.SourceReader.read(SourceReader.scala:40)
at scala.tools.nsc.io.SourceReader.read(SourceReader.scala:49)
at scala.tools.nsc.Global.getSourceFile(Global.scala:395)
at scala.tools.nsc.Global.getSourceFile(Global.scala:401)
at scala.tools.nsc.Global$Run$$anonfun.apply(Global.scala:1607)
at scala.tools.nsc.Global$Run$$anonfun.apply(Global.scala:1607)
at scala.collection.immutable.List.map(List.scala:284)
at scala.tools.nsc.Global$Run.compile(Global.scala:1607)
at scala.tools.nsc.StandardCompileServer.session(CompileServer.scala:151)
at scala.tools.util.SocketServer$$anonfun$doSession$$anonfun$apply.apply(SocketServer.scala:74)
at scala.tools.util.SocketServer$$anonfun$doSession$$anonfun$apply.apply(SocketServer.scala:74)
at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
at scala.Console$.withOut(Console.scala:65)
at scala.tools.util.SocketServer$$anonfun$doSession.apply(SocketServer.scala:74)
at scala.tools.util.SocketServer$$anonfun$doSession.apply(SocketServer.scala:69)
at scala.tools.nsc.io.Socket.applyReaderAndWriter(Socket.scala:49)
at scala.tools.util.SocketServer.doSession(SocketServer.scala:69)
at scala.tools.util.SocketServer.loop(SocketServer.scala:85)
at scala.tools.util.SocketServer.run(SocketServer.scala:97)
at scala.tools.nsc.CompileServer$$anonfun$execute$$anonfun$apply$mcZ$sp.apply$mcZ$sp(CompileServer.scala:218)
at scala.tools.nsc.CompileServer$$anonfun$execute$$anonfun$apply$mcZ$sp.apply(CompileServer.scala:213)
at scala.tools.nsc.CompileServer$$anonfun$execute$$anonfun$apply$mcZ$sp.apply(CompileServer.scala:213)
at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
at scala.Console$.withOut(Console.scala:53)
at scala.tools.nsc.CompileServer$$anonfun$execute.apply$mcZ$sp(CompileServer.scala:213)
at scala.tools.nsc.CompileServer$$anonfun$execute.apply(CompileServer.scala:213)
at scala.tools.nsc.CompileServer$$anonfun$execute.apply(CompileServer.scala:213)
at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
at scala.Console$.withErr(Console.scala:80)
at scala.tools.nsc.CompileServer$.execute(CompileServer.scala:212)
at scala.tools.nsc.CompileServer$.main(CompileServer.scala:180)
at scala.tools.nsc.CompileServer.main(CompileServer.scala)

似乎 Scala 正在编译我的脚本附近的东西,但我不太知道如何调试和修复它。

TL;DR

Ubuntu 的 Scala 包过去与 Java 8 不兼容(这已在 2.11.12-4 中修复)。解决方案是卸载 Ubuntu 的 Scala 包并安装 official Scala packages 之一。这一次您可能仍想这样做,不是因为与 Java 不兼容,而是因为 Ubuntu 最新打包的 Scala 版本仍然是 2.11,而 Scala 的最新版本目前是 2.13。

sudo apt remove scala-library scala
wget https://downloads.lightbend.com/scala/2.13.4/scala-2.13.4.deb
sudo dpkg -i scala-2.13.4.deb

由于很多人都在问这个问题背后的原因,我也很好奇是什么原因造成的,所以我做了一些挖掘...

问题的根源

在Java 9中,Buffer subclasses(包括ByteBuffer)被更改为覆盖superclass return Buffer中的方法到return 相应的子类型。

错误:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-4774077
提交:https://github.com/AdoptOpenJDK/openjdk-jdk9/commit/d9d7e875470bf478110b849315b4fff55b4c35cf

此更改不是二进制向后兼容的。如果在 Buffer 的子 classes 之一中直接调用这些方法的一些 Java 代码是用 JDK9+ 编译的,生成的字节码将不会 运行 在 JRE8 中(即使如果根本不使用 returned 值)。发生这种情况是因为调用时方法的签名将被编译为 JRE8 中不存在的 java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer。但是,如果使用 JDK8 编译,则编译成字节码的签名将是 java/nio/ByteBuffer.clear:()Ljava/nio/Buffer,它存在于 JRE8 和 JRE9+ 的 Buffer calss 中。

Scala哪里出错了? (或者做了?)

Scala 编译器确实使用了一些受上述更改影响的方法。特别是,在 SourceReader class 中,OP 的问题发生了错误。

查看 Scala's compatibility matrix,它说我们至少需要 Scala 2.11.12 才能使用 JDK11,但它并没有明确说明兼容性的相反方向。它确实说“Scala 2.12+ 绝对不能在 JDK 6 或 7 上工作”,所以我们可以预期 2.12+ 仍然与 JDK8 兼容,Scala 更是如此2.11.

那他们为什么要破坏兼容性呢?他们不能用旧的 JDK 版本编译 Scala 的源代码吗?他们没有,而且他们可以做到,以至于他们仍然这样做。

如果我们下载一个官方 Scala 包并检查清单文件中的 scala-compiler.jar,我们会发现:

Scala 2.11.12:

Bundle-Name: Scala Compiler
Bundle-RequiredExecutionEnvironment: JavaSE-1.6, JavaSE-1.7
Bundle-SymbolicName: org.scala-lang.scala-compiler
Bundle-Version: 2.11.12.v20171031-225310-b8155a5502
Created-By: 1.6.0_45 (Sun Microsystems Inc.)

Scala 2.13.4:

Bundle-Name: Scala Compiler
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-SymbolicName: org.scala-lang.scala-compiler
Bundle-Version: 2.13.4.v20201117-181115-VFINAL-39148e4
Created-By: 1.8.0_275 (AdoptOpenJDK)

所以 Scala 2.11 似乎仍在使用 JDK6 进行编译,而 Scala 2.13 仍在使用 JDK8 进行编译。这不应该意味着它们都与 JRE8 兼容吗?是的,确实如此。那么错误来自哪里?

Ubuntu哪里出错了?

Ubuntu 与大多数其他 Linux 发行版一样,喜欢构建自己的包,并通过其包管理器提供。这样做是为了确保 OS 生态系统中的一切正常运行,这通常意味着修补上游项目的源代码。

特别是关于 Scala 包,Ubuntu 决定放弃用于编译 Scala 源代码的 JDK 版本的上游选择和 has been using newer JDK versions to compile Ubuntu's Scala package for a while.

如果我们在 Ubuntu 的 Scala 2.11.12-4 中检查 scala-compiler.jar 的清单文件,我们可以看到它是用 JDK11:[=28 编译的=]

Created-By: 11.0.2+9-Ubuntu-3ubuntu1 (Oracle Corporation)
Bundle-Name: Scala Distribution
Bundle-SymbolicName: org.scala-ide.scala.compiler;singleton:=true
Bundle-Version: 2.11.12

你不是说这个问题在 2.11.12-4 中已经解决了吗?是的,我做到了。

Ubuntu解决这个问题的方法不是用JDK8编译Scala,而是给Scala的源代码打补丁,避免直接在sub[=77=中调用有问题的方法]是的。这是通过在调用这些方法之前将 ByteBuffer(和 CharBuffer)转换为它的 superclass Buffer 来实现的。在实践中,这意味着将 Scala 的源代码从 bytes.clear() 更改为 bytes.asInstanceOf[Buffer].clear().asInstanceOf[ByteBuffer](不知道为什么当 clear() 的结果似乎根本没有被使用时他们将其转换回 ByteBuffer) .这里是 Ubuntu's patch.

Ubuntu 的方法似乎有点危险,因为其他不兼容的来源可能没有被注意到,并且在某些非常具体的情况下仍然存在等待发生。他们自己的设置也不同于官方 Scala 版本,这意味着没有整个 Scala 社区在实际场景中测试这些变化。

通过禁用版本 2.11.12 的 fsc 对我有效:

#!/usr/bin/env -S scala -nc