首先,让我提出一个(非常)简化的版本,我想要实现的目标。 考虑以下多项目:
root
|___backend
|
|___frontend
|
|___deployer
后端与oneJar一起打包,并作为执行后台工作的独立进程执行。前端是一个Web服务play项目(打包为带dist
的zip文件)。部署者是另一个与oneJar一起打包的自执行jar,但是包装被修改为包含其他项目工件。部署工作是初始化系统。它将其他工件部署到指定的计算机,并初始化分布式系统。
deployer-executable.jar
|___0/
| |___backend-selfexec.jar
| |___frontenf-dist.zip
|
|___1/
| |___other resources (mostly configuration files)
| |___ ...
|
|___META-INF/ ...
|___com/simontuffs/onejar/ ...
|___doc/ ...
|___lib/ ...
|___main/deployer-VERSION.jar
|___other resources (such as logback.xml) and oneJar files....
...
lazy val backend = project in file("backend")
lazy val frontend = project in file("frontend")
lazy val deployer = project in file("deployer") dependsOn(backend % "optional->compile", frontend % "optional->compile") aggregate(backend, frontend)
...
...
seq(com.github.retronym.SbtOneJar.oneJarSettings: _*)
exportJars := true
mainClass in oneJar := Some("org.product.backend.Main")
artifact in oneJar <<= moduleName(Artifact(_, "selfexec"))
addArtifact(artifact in (Compile, oneJar), oneJar)
...
import play.Project._
...
play.Project.playScalaSettings
lazy val dist = com.typesafe.sbt.SbtNativePackager.NativePackagerKeys.dist
lazy val publishDist = TaskKey[sbt.File]("publish-dist", "publish the dist artifact")
publish <<= (publish) dependsOn dist
publishLocal <<= (publishLocal) dependsOn dist
artifact in publishDist ~= {
(art: Artifact) => art.copy(`type` = "zip", extension = "zip", classifier = Some("dist"))
}
publishDist <<= (target in Universal, normalizedName, version) map { (targetDir, id, version) =>
val packageName = s"$id-$version"
targetDir / (packageName + ".zip")
}
addArtifact(artifact in publishDist, publishDist)
...
...
seq(com.github.retronym.SbtOneJar.oneJarSettings: _*)
exportJars := true
mainClass in oneJar := Some("org.product.deployer.Main")
unmanagedResources in Compile := Seq() //don't add resources from "src/main/resources" to inner jar, only to the fat one-jar.
classpathTypes := classpathTypes.value + "zip" //don't ommit the dist zip file from classpath
mappings in oneJar := {
def isNeedToBeInDir0(f: File) = f.getName == "frontend-VERSION-dist.zip" || f.getName == "backend-VERSION-selfexec.jar"
def nameForPackaging(name: String): String = if(name.contains("frontend")) "frontend.zip" else "backend.jar"
//following method could be replaced with: http://www.scala-sbt.org/release/docs/Detailed-Topics/Mapping-Files.html#relative-to-a-directory
def files2TupleRec(pathPrefix: String, dir: File): Seq[Tuple2[File,String]] = {
sbt.IO.listFiles(dir) flatMap {
f => {
if(f.isFile) Seq((f,s"${pathPrefix}${f.getName}"))
else files2TupleRec(s"${pathPrefix}${f.getName}/",f)
}
}
}
val oldSeq = (mappings in oneJar).value
oldSeq.filterNot(t => isNeedToBeInDir0(t._1)) ++
oldSeq.filter(t => isNeedToBeInDir0(t._1)).map{
case (f,_) => (f,s"/0/${nameForPackaging(f.getName)}") //NOT WORKING
} ++
files2TupleRec("",file("deployer/src/main/resources"))
}
//following lines is commented out because it's also not working, but it shows very clearly what i'm trying to do.
//(types are wrong. i need File but have sbt.Artifact):
//mappings in oneJar <+= (artifact in LocalProject("backend") in oneJar) map {_ -> "/0/backend.jar"}
//mappings in oneJar <+= (artifact in LocalProject("frontend") in oneJar) map {_ -> "/0/frontend.zip"}
artifact in oneJar <<= moduleName(Artifact(_, "executable"))
addArtifact(artifact in (Compile, oneJar), oneJar)
...
请注意,在root build.sbt中,我有per-configuration classpath dependencies "optional->compile"
。看完ivy.xml
文件后,我把它放在那里:
...
<configurations>
<conf name="compile" visibility="public" description=""/>
<conf name="runtime" visibility="public" description="" extends="compile"/>
<conf name="test" visibility="public" description="" extends="runtime"/>
<conf name="provided" visibility="public" description=""/>
<conf name="optional" visibility="public" description=""/>
<conf name="sources" visibility="public" description=""/>
<conf name="pom" visibility="public" description=""/>
</configurations>
<publications>
<artifact name="frontend_2.10" type="zip" ext="zip" conf="compile,runtime,test,provided,optional,sources,pom" e:classifier="dist"/>
<artifact name="frontend_2.10" type="pom" ext="pom" conf="pom"/>
<artifact name="frontend_2.10" type="jar" ext="jar" conf="compile"/>
<artifact name="frontend_2.10" type="src" ext="jar" conf="sources" e:classifier="sources"/>
</publications>
...
并且看到dist.zip
工件只在optional
范围内找到,我想我可以通过这种方式将这个工件作为依赖(虽然似乎没有工作......)。另外,当我试用注释掉的行时,我得到一个错误,说它是错误的类型。
我错过了一些东西(很多片段都是复制和粘贴的......)。首先,部署者不应该dependOn(backend,frontend)
。只是aggregate
。而且,这些工件(后端和前端)根本不应该在部署者的类路径中可见。所以这一行:
classpathTypes := classpathTypes.value + "zip"
是不需要的。另外,转换mappings in oneJar
文件中deployer/build.sbt
的代码可能要简单得多。只需要照顾资源。最后,注释掉的行应该是:
mappings in oneJar <+= (artifactPath in LocalProject("backend") in oneJar) map {_ -> "0/backend.jar"}
mappings in oneJar <+= (packageBin in LocalProject("frontend") in Universal) map {_ -> "0/frontend.zip"}
几乎就是这样。