【问题标题】:How to make a SBT task depend on a module defined in the same SBT project?如何使 SBT 任务依赖于同一个 SBT 项目中定义的模块?
【发布时间】:2018-05-05 06:09:49
【问题描述】:

我在一个多模块 SBT 项目中有模块 A 和模块 B。我想为模块 B 编写一个资源生成器任务,该任务从模块 A 调用代码。一种方法是从模块 A 中提取所有代码 project/ 但这是不可行的,因为模块 A 很大,我想将其保留在原处(请参阅https://stackoverflow.com/a/47323703/471136)。如何在 SBT 中执行此操作?

其次,是否有可能完全摆脱模块 B,即我希望模块 A 的资源生成器任务实际上从模块 A 调用代码,但模块 A 是根模块并且不存在于 SBT 的project 中?

注意:这个问题不是这个问题的重复:Defining sbt task that invokes method from project code?,因为这个问题决定将代码移到 SBT 的项目中,这是我在这里特别想避免的。

【问题讨论】:

    标签: scala sbt sbt-assembly sbt-plugin


    【解决方案1】:

    认为我正在做类似于您在以下项目中的第一部分的事情:我的 module gen 相当于您的模块 A,我的 module core 相当于您的模块 B . 未经测试,结构大致如下:

    // taking inspiration from
    // http://stackoverflow.com/questions/11509843/
    lazy val ugenGenerator = TaskKey[Seq[File]]("ugen-generate", "Generate UGen class files")
    
    lazy val gen = Project(id = "gen", base = file("gen")) ...
    
    lazy val core = Project(id = "core", base = file("core"))
      .settings(
        sourceGenerators in Compile <+= ugenGenerator in Compile,
        ugenGenerator in Compile := {
          val src   = (sourceManaged       in Compile        ).value
          val cp    = (dependencyClasspath in Runtime in gen ).value
          val st    = streams.value
          runUGenGenerator(description.value, outputDir = src, 
            cp = cp.files, log = st.log)
        }
      )
    
    def runUGenGenerator(name: String, outputDir: File, cp: Seq[File],
                        log: Logger): Seq[File] = {
      val mainClass   = "my.class.from.Gen"
      val tmp         = java.io.File.createTempFile("sources", ".txt")
      val os          = new java.io.FileOutputStream(tmp)
    
      log.info(s"Generating UGen source code in $outputDir for $name")
    
      try {
        val outs  = CustomOutput(os)
        val fOpt  = ForkOptions(javaHome = None, outputStrategy = Some(outs), bootJars = cp,
            workingDirectory = None, connectInput = false)
        val res: Int = Fork.scala(config = fOpt,
          arguments = mainClass :: "-d" :: outputDir.getAbsolutePath :: Nil)
    
        if (res != 0) {
          sys.error(s"UGen class file generator failed with exit code $res")
        }
      } finally {
        os.close()
      }
      val sources = scala.io.Source.fromFile(tmp).getLines().map(file).toList
      tmp.delete()
      sources
    }
    

    这在 sbt 0.13 中有效,但我没有时间弄清楚为什么它在 1.x 中无效。

    顺便说一句,我如何在没有弃用语法的情况下编写sourceGenerators in Compile &lt;+= ugenGenerator in Compile

    【讨论】:

    • 我认为你可以用lhs += foo.value 摆脱lhs &lt;+= foo 无论如何,当我尝试这个时我得到了这个:Error: Could not find or load main class scala.tools.nsc.MainGenericRunner java.lang.IllegalArgumentException: requirement failed: com.github.pathikrit.ResourceGenerator
    • 这是否意味着我必须将 Scala 编译器本身作为依赖项添加到某处???
    • 不确定,但我的“gen”模块确实添加了编译器:"org.scala-lang" % "scala-compiler" % scalaVersion.value(不记得为什么)
    • 嗯,还有一件事——模块 A 本身从它自己的资源中加载了一些其他的东西。当我从模块 A 中执行 getClass.getResourceAsStream(foo) 时,使用 Fork.scala 调用模块 A 似乎会引发 NPE。我该如何解决这个问题?我需要以某种方式将资源传递给 ForkOption 吗??
    • 不确定,但the docs 建议您可能需要fullClasspath
    【解决方案2】:

    根据上面的@0__ 回答,这是我的简化版本:

      /**
        * Util to run the main of a sub-module from within SBT
        *
        * @param cmd The cmd to run with the main class with args (if any)
        * @param module The sub-module
        */
      def runModuleMain(cmd: String, module: Reference) = Def.task {
        val log = streams.value.log
        log.info(s"Running $cmd ...")
        val classPath = (fullClasspath in Runtime in module).value.files
        val opt = ForkOptions(bootJars = classPath, outputStrategy = Some(LoggedOutput(log)))
        val res = Fork.scala(config = opt, arguments = cmd.split(' '))
        require(res == 0, s"$cmd exited with code $res")
      }
    

    然后你可以调用它:

    resourceGenerators in Compile += Def.taskDyn {
      val dest = (resourceManaged in Compile).value
      IO.createDirectory(dest)
      runModuleMain(
        cmd = "com.mycompany.foo.ResourceGen $dest $arg2 $arg3 ...",
        module = $referenceToModule // submodule containing com.mycompany.foo.ResourceGen
      ).taskValue
      dest.listFiles()
    }
    

    one more way可以做到这一点:

    resourceGenerators in Compile += Def.taskDyn {
      val dest = (resourceManaged in Compile).value
      IO.createDirectory(dest)      
      Def.task {
        val cmd = "com.mycompany.foo.ResourceGen $dest $arg2 $arg3 ..."        
        (runMain in Compile).toTask(" " + $cmd).value 
        dest.listFiles()
      }
    }.taskValue
    

    【讨论】:

      猜你喜欢
      • 2020-05-15
      • 2020-04-11
      • 1970-01-01
      • 2016-12-18
      • 2015-11-10
      • 1970-01-01
      • 2018-03-20
      • 1970-01-01
      • 2015-09-13
      相关资源
      最近更新 更多