【问题标题】:How can I run DataNucleus Bytecode Enhancer from SBT?如何从 SBT 运行 DataNucleus Bytecode Enhancer?
【发布时间】:2015-05-13 14:34:12
【问题描述】:

我已经整理了一个概念证明,旨在提供一个骨架 SBT 多模块项目,该项目利用 DataNucleus JDO Enhancer 和混合 Java 和 Scala 源。

当我尝试从 SBT 增强持久性类时出现了困难。显然,当从 SBT 调用 Fork.java.fork(...) 时,我没有传递正确的类路径。


另请参阅此问题:
How can SBT generate metamodel classes from model classes using DataNucleus?


Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.datanucleus.util.Localiser
        at org.datanucleus.metadata.MetaDataManagerImpl.loadPersistenceUnit(MetaDataManagerImpl.java:1104)
        at org.datanucleus.enhancer.DataNucleusEnhancer.getFileMetadataForInput(DataNucleusEnhancer.java:768)
        at org.datanucleus.enhancer.DataNucleusEnhancer.enhance(DataNucleusEnhancer.java:488)
        at org.datanucleus.api.jdo.JDOEnhancer.enhance(JDOEnhancer.java:125)
        at javax.jdo.Enhancer.run(Enhancer.java:196)
        at javax.jdo.Enhancer.main(Enhancer.java:130)
[info] Compiling 2 Java sources to /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/klasses...
java.lang.IllegalStateException: errno = 1
        at $54321831a5683ffa07b5$.runner(build.sbt:230)
        at $54321831a5683ffa07b5$$anonfun$model$7.apply(build.sbt:259)
        at $54321831a5683ffa07b5$$anonfun$model$7.apply(build.sbt:258)
        at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
        at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40)
        at sbt.std.Transform$$anon$4.work(System.scala:63)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
        at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
        at sbt.Execute.work(Execute.scala:235)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
        at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
        at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)

为了完整和信息,您可以在下面看到一个由 SBT 生成的 java 命令行,例如,可以在单独的窗口上手动执行。它工作正常。

$ java  -cp /home/rgomes/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.6.jar:/home/rgomes/.ivy2/cache/com.google.code.gson/gson/jars/gson-2.3.1.jar:/home/rgomes/.ivy2/cache/javax.jdo/jdo-api/jars/jdo-api-3.0.jar:/home/rgomes/.ivy2/cache/javax.transaction/transaction-api/jars/transaction-api-1.1.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-core/jars/datanucleus-core-4.0.4.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-api-jdo/jars/datanucleus-api-jdo-4.0.4.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-jdo-query/jars/datanucleus-jdo-query-4.0.4.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-rdbms/jars/datanucleus-rdbms-4.0.4.jar:/home/rgomes/.ivy2/cache/com.h2database/h2/jars/h2-1.4.185.jar:/home/rgomes/.ivy2/cache/org.postgresql/postgresql/jars/postgresql-9.4-1200-jdbc41.jar:/home/rgomes/.ivy2/cache/com.github.dblock.waffle/waffle-jna/jars/waffle-jna-1.7.jar:/home/rgomes/.ivy2/cache/net.java.dev.jna/jna/jars/jna-4.1.0.jar:/home/rgomes/.ivy2/cache/net.java.dev.jna/jna-platform/jars/jna-platform-4.1.0.jar:/home/rgomes/.ivy2/cache/org.slf4j/slf4j-simple/jars/slf4j-simple-1.7.7.jar:/home/rgomes/.ivy2/cache/org.slf4j/slf4j-api/jars/slf4j-api-1.7.7.jar:/home/rgomes/workspace/poc-scala-datanucleus/model/src/main/resources:/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/klasses javax.jdo.Enhancer -v -pu persistence-h2 -d /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes

May 13, 2015 3:30:07 PM org.datanucleus.enhancer.ClassEnhancerImpl save
INFO: Writing class file "/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes/model/AbstractModel.class" with enhanced definition
May 13, 2015 3:30:07 PM org.datanucleus.enhancer.DataNucleusEnhancer addMessage
INFO: ENHANCED (Persistable) : model.AbstractModel
May 13, 2015 3:30:07 PM org.datanucleus.enhancer.ClassEnhancerImpl save
INFO: Writing class file "/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes/model/Identifier.class" with enhanced definition
May 13, 2015 3:30:07 PM org.datanucleus.enhancer.DataNucleusEnhancer addMessage
INFO: ENHANCED (Persistable) : model.Identifier
May 13, 2015 3:30:07 PM org.datanucleus.enhancer.DataNucleusEnhancer addMessage
INFO: DataNucleus Enhancer completed with success for 2 classes. Timings : input=112 ms, enhance=102 ms, total=214 ms. Consult the log for full details
Enhancer Processing -v.
Enhancer adding Persistence Unit persistence-h2.
Enhancer processing output directory /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes.
Enhancer found JDOEnhancer of class org.datanucleus.api.jdo.JDOEnhancer.
Enhancer property key:VendorName value:DataNucleus.
Enhancer property key:VersionNumber value:4.0.4.
Enhancer property key:API value:JDO.
Enhancer enhanced 2 classes.

您可以在下面看到一些传递给 Fork.java.fork(...) 的调试信息:

=============================================================
mainClass=javax.jdo.Enhancer
args=-v -pu persistence-h2 -d /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes
javaHome=None
cwd=/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes
runJVMOptions=
bootJars ---------------------------------------------
/home/rgomes/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.6.jar
/home/rgomes/.ivy2/cache/com.google.code.gson/gson/jars/gson-2.3.1.jar
/home/rgomes/.ivy2/cache/javax.jdo/jdo-api/jars/jdo-api-3.0.jar
/home/rgomes/.ivy2/cache/javax.transaction/transaction-api/jars/transaction-api-1.1.jar
/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-core/jars/datanucleus-core-4.0.4.jar
/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-api-jdo/jars/datanucleus-api-jdo-4.0.4.jar
/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-jdo-query/jars/datanucleus-jdo-query-4.0.4.jar
/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-rdbms/jars/datanucleus-rdbms-4.0.4.jar
/home/rgomes/.ivy2/cache/com.h2database/h2/jars/h2-1.4.185.jar
/home/rgomes/.ivy2/cache/org.postgresql/postgresql/jars/postgresql-9.4-1200-jdbc41.jar
/home/rgomes/.ivy2/cache/com.github.dblock.waffle/waffle-jna/jars/waffle-jna-1.7.jar
/home/rgomes/.ivy2/cache/net.java.dev.jna/jna/jars/jna-4.1.0.jar
/home/rgomes/.ivy2/cache/net.java.dev.jna/jna-platform/jars/jna-platform-4.1.0.jar
/home/rgomes/.ivy2/cache/org.slf4j/slf4j-simple/jars/slf4j-simple-1.7.7.jar
/home/rgomes/.ivy2/cache/org.slf4j/slf4j-api/jars/slf4j-api-1.7.7.jar
/home/rgomes/workspace/poc-scala-datanucleus/model/src/main/resources
/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/klasses
envVars ----------------------------------------------

=============================================================

为了您的方便,该项目在 github 上可用 https://github.com/frgomes/poc-scala-datanucleus

只需下载并输入

./sbt compile

非常感谢任何帮助。谢谢

【问题讨论】:

  • 为什么不在消息 org.datanucleus.util.Localiser 中关注该类以及它使用了哪些类(NoClassDefFoundError 表示不存在)。
  • @NeilStockton :: 问题是我将类路径作为 -Xbootclasspath 而不是应有的 -classpath 传递。我想这与 Localiser 具有静态代码初始化这一事实有关,这在某些情况下是困难的根源。
  • 我的答案有一个概念证明的链接,它使用 SBT,现在工作正常。如果您觉得有用,您可以将其合并到 DataNucleus 手册或教程中。谢谢
  • 我与 DataNucleus 项目无关,我使用它,就像你一样。如果您想贡献它,建议您与他们交谈

标签: java scala sbt datanucleus bytecode-manipulation


【解决方案1】:

您可以使用java.lang.ProcessBuildersbt.Fork

请参阅下面的通用javaRunner,您可以将其添加到使用java.lang.ProcessBuilder 的build.sbt。

另请参阅通用sbtRunner,您可以将其添加到使用sbt.Fork 的build.sbt。感谢@dwijnand 为使 sbtRunner 按预期工作提供了有见地的信息。

def javaRunner(mainClass: String,
               args: Seq[String],
               classpath: Seq[File],
               cwd: File,
               javaHome: Option[File] = None,
               runJVMOptions: Seq[String] = Nil,
               envVars: Map[String, String] = Map.empty,
               connectInput: Boolean = false,
               outputStrategy: Option[OutputStrategy] = Some(StdoutOutput)): Seq[File] = {

  val java_ : String      = javaHome.fold("") { p => p.absolutePath + "/bin/" } + "java"
  val jvm_  : Seq[String] = runJVMOptions.map(p => p.toString)
  val cp_   : Seq[String] = classpath.map(p => p.absolutePath)
  val env_                = envVars.map({ case (k,v) => s"${k}=${v}" })
  val xcmd_ : Seq[String] = Seq(java_) ++ jvm_ ++ Seq("-cp", cp_.mkString(java.io.File.pathSeparator), mainClass) ++ args

  println("=============================================================")
  println(xcmd_.mkString(" "))
  println("=============================================================")
  println("")

  IO.createDirectory(cwd)

  import scala.collection.JavaConverters._
  val cmd = xcmd_.asJava

  val pb = new java.lang.ProcessBuilder(cmd)
  pb.directory(cwd)
  pb.inheritIO
  val process = pb.start()
  def cancel() = {
    println("Run canceled.")
    process.destroy()
    1
  }
  val errno = try process.waitFor catch { case e: InterruptedException => cancel() }
  if(errno==0) {
    if (args.contains("-v")) cwd.list.foreach(f => println(f))
    cwd.listFiles
  } else {
    throw new IllegalStateException(s"errno = ${errno}")
  }
}

def sbtRunner(mainClass: String,
           args: Seq[String],
           classpath: Seq[File],
           cwd: File,
           javaHome: Option[File] = None,
           runJVMOptions: Seq[String] = Nil,
           envVars: Map[String, String] = Map.empty,
           connectInput: Boolean = false,
           outputStrategy: Option[OutputStrategy] = Some(StdoutOutput)): Seq[File] = {

  val args_ = args.map(p => p.toString)
  val java_ = javaHome.fold("None") { p => p.absolutePath }
  val cp_   = classpath.map(p => p.absolutePath)
  val jvm_  = runJVMOptions.map(p => p.toString) ++ Seq("-cp", cp_.mkString(java.io.File.pathSeparator))
  val env_  = envVars.map({ case (k,v) => s"${k}=${v}" })

  def dump: String =
    s"""
       |mainClass=${mainClass}
       |args=${args_.mkString(" ")}
       |javaHome=${java_}
       |cwd=${cwd.absolutePath}
       |runJVMOptions=${jvm_.mkString(" ")}
       |classpath --------------------------------------------
       |${cp_.mkString("\n")}
       |envVars ----------------------------------------------
       |${env_.mkString("\n")}
    """.stripMargin

  def cmd: String =
    s"""java ${jvm_.mkString(" ")} ${mainClass} ${args_.mkString(" ")}"""

  println("=============================================================")
  println(dump)
  println("=============================================================")
  println(cmd)
  println("=============================================================")
  println("")

  IO.createDirectory(cwd)
  val options =
    ForkOptions(
      javaHome = javaHome,
      outputStrategy = outputStrategy,
      bootJars = Seq.empty,
      workingDirectory = Option(cwd),
      runJVMOptions = jvm_,
      connectInput = connectInput,
      envVars = envVars)
  val process = new Fork("java", Option(mainClass)).fork(options, args)
  def cancel() = {
    println("Run canceled.")
    process.destroy()
    1
  }
  val errno = try process.exitValue() catch { case e: InterruptedException => cancel() }
  if(errno==0) {
    if (args.contains("-v")) cwd.list.foreach(f => println(f))
    cwd.listFiles
  } else {
    throw new IllegalStateException(s"errno = ${errno}")
  }
}

然后,您需要在构建过程中连接 DataNucleus Enhancer。这是通过manipulateBytecode 子任务完成的,如下所示:

lazy val model =
  project.in(file("model"))
    // .settings(publishSettings:_*)
    .settings(librarySettings:_*)
    .settings(paranoidOptions:_*)
    .settings(otestFramework: _*)
    .settings(deps_tagging:_*)
    //-- .settings(deps_stream:_*)
    .settings(deps_database:_*)
    .settings(
      Seq(
        // This trick requires SBT 0.13.8
        manipulateBytecode in Compile := {
          val previous = (manipulateBytecode in Compile).value
          sbtRunner(  // javaRunner also works!
            mainClass = "javax.jdo.Enhancer",
            args =
              Seq(
                "-v",
                "-pu", "persistence-h2",
                "-d",  (classDirectory in Compile).value.absolutePath),
            classpath =
              (managedClasspath in Compile).value.files ++
                (unmanagedResourceDirectories in Compile).value :+
                (classDirectory in Compile).value,
            cwd = (classDirectory in Compile).value,
            javaHome = javaHome.value,
            envVars = (envVars in Compile).value
          )
          previous
        }
      ):_*)
    .dependsOn(util)

如需完整示例,包括一些 JDO 注释的持久性类和一些基本的测试用例,请查看

http://github.com/frgomes/poc-scala-datanucleus

【讨论】:

    【解决方案2】:

    我认为问题在于您将依赖 jars 作为引导 jars 而不是作为类路径传递。

    来自您的 poc 项目,可能类似于:

    val jvm_ = runJVMOptions.map(p => p.toString) ++
      Seq("-cp", cp_ mkString java.io.File.pathSeparator)
    

    【讨论】:

    • 你能澄清一下吗?涉及 3 个“类路径”: 1. bootclasspath(作为 -X jvm arg 传递),它依赖于平台,我没有使用它; 2. -cp(相当于 -classpath),相当于 ForkOptions.bootJars、AFAIK 和 3. javax.jdo.Enhancer 接受我猜想在实例化 ClassLoader 时使用的参数“-cp”,以便提供子类加载器的附加条目。你到底在说什么?
    • 不,只有 2 个:bootJars 用于的引导类路径(请参阅github.com/sbt/sbt/blob/v0.13.8/run/src/main/scala/sbt/…)和类路径,即“-cp”,因此您想停止使用 bootJars 并使用“-cp”。
    • 啊! ... ForkOptions.bootJars 最后变成 -Xbootclasspath :-( ...这太令人困惑了...如果 ForkOptions.bootJars 存在,为什么不 ForkOptions.classpath?我猜两者都应该存在或都不应该存在,因为开发人员总是可以根据需要编造参数。无论如何...我会尝试并让你知道。非常感谢:-)
    • 是的,使用.classpath 可能会更好。随意提出功能请求,也许核心开发人员对该领域有更深入的了解。
    • 感谢您的推荐,现在可以使用了。但我已将类路径附加到 runJVMOptions。类似,但不完全是您最初推荐的。我已经将一个新的 build.sbt 推送到 github,以防您想查看更改并更新您的答案。我将更改我的答案并参考您的提示。非常感谢:-)
    猜你喜欢
    • 2015-05-30
    • 1970-01-01
    • 1970-01-01
    • 2014-03-11
    • 1970-01-01
    • 1970-01-01
    • 2010-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多