如何在Scala对象中从sbt的ProjectPlugin.scala获取应用Jar包名称?
我来帮你梳理下这个问题的解决方案哈~首先得明确:你直接想在运行时的Scala代码里import ProjectPlugin.scala里的内容是行不通的,因为ProjectPlugin是sbt的构建定义代码,属于构建时的逻辑,根本不会被打包到你的应用Jar包里,运行时根本找不到这个类。下面给你几个实用的替代方案:
方案一:构建时自动生成包含name/version的源文件(最推荐)
这个方法能让运行时代码直接读取和构建配置完全同步的name和version,不用手动维护,非常可靠。
你可以在ProjectPlugin.scala里添加一个源码生成任务,让sbt在编译阶段自动生成一个包含项目信息的Scala对象:
import sbt._ import Keys._ object ProjectPlugin extends AutoPlugin { override def trigger = allRequirements override def projectSettings = Seq( // 自动生成AppInfo.scala到编译源目录 sourceGenerators in Compile += Def.task { val outputDir = (sourceManaged in Compile).value val appInfoFile = outputDir / "com" / "yourcompany" / "AppInfo.scala" // 这里的name和version就是你在ProjectPlugin里定义的配置 val content = s"""package com.yourcompany |object AppInfo { | val name = "${name.value}" | val version = "${version.value}" |}""".stripMargin // 写入文件 IO.write(appInfoFile, content) Seq(appInfoFile) }.taskValue, // 你的原有配置 name := "project1", version := "v1" ) }
之后在你的Test.scala里,就可以直接导入这个自动生成的对象来获取Jar名称了:
import com.yourcompany.AppInfo object Test extends App { val jarName = s"${AppInfo.name}-${AppInfo.version}.jar" println(s"当前应用Jar名称:$jarName") }
每次你更新ProjectPlugin里的version或者name,编译时都会自动生成最新的AppInfo.scala,完全同步,不会出错。
方案二:读取Jar包的Manifest元数据
sbt-assembly插件可以把项目的name和version写入Jar包的Manifest文件中,运行时通过类加载器读取这些元数据。
首先在ProjectPlugin.scala里配置assembly的Manifest属性:
import sbt._ import Keys._ import sbtassembly.AssemblyKeys._ object ProjectPlugin extends AutoPlugin { override def projectSettings = Seq( assemblyOption in assembly := (assemblyOption in assembly).value.copy( includeScala = false, // 把name和version写入Manifest manifest = (manifest in assembly).value .withAttribute("Implementation-Title", name.value) .withAttribute("Implementation-Version", version.value) ), // 原有配置 name := "project1", version := "v1" ) }
然后在Test.scala里读取Manifest信息:
object Test extends App { val jarName = try { // 获取当前类所在的Jar包的Manifest val codeSource = classOf[Test].getProtectionDomain.getCodeSource val jarStream = codeSource.getLocation.openStream() val manifest = new java.util.jar.JarInputStream(jarStream).getManifest() jarStream.close() val appName = manifest.getMainAttributes.getValue("Implementation-Title") val appVersion = manifest.getMainAttributes.getValue("Implementation-Version") s"$appName-$appVersion.jar" } catch { case e: Exception => // 处理非Jar运行的情况(比如IDE里调试) "unknown-app.jar" } println(s"当前应用Jar名称:$jarName") }
这个方案不用生成额外的源文件,但要注意如果是在IDE里直接运行(不是打包成Jar),这段代码可能会抛出异常,需要加异常处理。
方案三:启动时传递系统属性(不推荐)
这个方法需要在启动Jar的时候手动传递name和version作为系统属性,比如:
java -Dapp.name=project1 -Dapp.version=v1 -jar project1-v1.jar
然后在Test.scala里读取系统属性:
object Test extends App { val appName = sys.props.getOrElse("app.name", "unknown") val appVersion = sys.props.getOrElse("app.version", "unknown") val jarName = s"$appName-$appVersion.jar" println(s"当前应用Jar名称:$jarName") }
这个方案的缺点很明显:需要手动维护启动参数,容易和ProjectPlugin里的配置不一致,除非是特殊场景,否则不推荐使用。
内容的提问来源于stack exchange,提问作者user400058

