从 Scala 程序中查找 Scala 库的位置

Finding Scala libraries location from within Scala program

我正在尝试让一个 Scala 程序产生另一个 Scala 程序。我设法从 System.getProperty("java.home") 获得了 java 可执行文件,我从 System.getProperty("java.class.path")sbt-launcher.jar 位置)获得了一些路径,并且 ClassLoader 我得到了 project/target/scala-2.11/classes 目录.

但是,我还是无法运行。 JVM 抱怨它无法找到 Scala 库的 类:

Exception in thread "main" java.lang.NoClassDefFoundError: scala/concurrent/ExecutionContext
  at java.lang.Class.getDeclaredMethods0(Native Method)
  at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
  at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
  at java.lang.Class.getMethod0(Class.java:3018)
  at java.lang.Class.getMethod(Class.java:1784)
  at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Caused by: java.lang.ClassNotFoundException: scala.concurrent.ExecutionContext
  at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
  at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 7 more

我正在寻找将这些文件添加到类路径的方法,但我希望它是可移植的。我不想在本地计算机上寻找硬编码 scala 位置之类的解决方案,也不想使用现有环境变量和参数之外的其他环境变量和参数。我也不想依赖 SBT 或 Activators 在用户环境中的存在。

由于父 JVM 进程可以使用它们,因此它们的位置必须存储在某个地方,如果您能帮助我找到该位置,我将不胜感激。

为了成功地从另一个 Scala 应用程序生成一个 Scala 应用程序,我必须解决我的代码中的几个问题:

1。正确 main class:

object ChildApp extends App {
  println("success")
}

要确保 ChildApp 可以由 Java 运行,它必须是 object。 Scala 没有 static 的概念,但是对象方法(和 main 将)被编译成静态方法。

2。正确的 class 姓名:

虽然 ChildApp.getClass.getName returns ChildApp$,它指的是一个对象(这样我们就可以传递其他静态方法-only class)。 Java 期望在命令行中使用 $ - 在其他工作中,我必须在将尾部 $ 传递到流程构建器之前将其删除。

3。完整 class 路径

我没有在 System.getPropertiy("java.class.path"):

中找到所有使用过的 JAR
val pcp = System getPropertiy "java.class.path" split File.pathSeparator // sbt-launcher.jar only

我也没有在 SystemClassLoader 中找到它们:

val scp = ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.toString) // same as above

我确实使用 Class' 资源从我的项目中找到了编译文件:

// format like jar:file:/(your-project/compiled.jar)!some/package/ChildApp.class
lazy val jarClassPathPattern  = "jar:(file:)?([^!]+)!.+".r
// format like file:/(your-project/compiled/some/package/ChildApp).class
lazy val fileClassPathPattern = "file:(.+).class".r

val jcp = jarClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
  val jarDir = Paths get (matcher group 2) getParent()
  s"${jarDir}/*"
} toSet

val fcp = fileClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
  val suffix   = "/" + clazz.getName
  val fullPath = matcher group 1
  fullPath substring (0, fullPath.length - suffix.length)
} toList

最后我找到了所有这些依赖项的存储位置:

// use App class' ClassLoader instead of system one
val lcp = ChildApp.getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.toString)

4。奖励 - JVM 参数和 java 位置

val jvmArgs   = ManagementFactory.getRuntimeMXBean.getInputArguments.toList

lazy val javaHome = System getProperty "java.home"

lazy val java = Seq(
  Paths.get(javaHome, "bin", "java"),
  Paths.get(javaHome, "bin", "java.exe")
) filter (Files exists _) head

那么你就拥有了 ProcessBuilder / Process:

所需的一切
val executable = java.toString
val arguments  = jvmArgs ++ List("-cp", classPath, mainName) ++ mainClassArguments

PS。我检查了几次 - 那些 附加的 JAR 既没有使用 CLASSPATH 环境变量也没有使用 -cp 参数传递(sbt-launcher.jar 的 MANIFEST 文件没有'也有任何东西)。所以任何知道它们是如何通过的以及为什么我的解决方案实际有效的人,请解释一下。