1

what am I trying to do:

first, let me present a (very) simplified version of what i'm trying to achieve. consider the following multi-project:

root
|___backend
|   
|___frontend
|
|___deployer

the backend is packaged with oneJar, and performs as a standalone process that does the background work. the frontend is a web-service play project (packaged as a zip file with dist). the deployer is yet another self executable jar that is packaged with oneJar, but the packaging is modified to include the other projects artifacts. the deployer job is to initialize the system. it deploys the other artifacts to specified machines, and initializes the distributed system.

what is my problem:

basically, i'm trying (unsuccessfully) to get the play's dist zip artifact & the backend oneJar self executable artifact packaged inside the deployer jar (with other resources files)

the deployer jar should look like:

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....

what do I have so far:

build.sbt

...

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)

...

backend/build.sbt

...

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)

...

frontend/build.sbt

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)

...

deployer/build.sbt

...

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)

...

notice that in the root build.sbt, i have per-configuration classpath dependencies "optional->compile". Iv'e put it there after looking at the ivy.xml file:

...
 <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>
...

and seeing that the dist.zip artifact is only found in optional scope, I thought I could get this artifact as a dependency that way (not seem to be working though...). also, when i tried out the commented out lines, i got an error saying it's the wrong type.

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

as i'm writing the question, i figured out what I've done wrong...

UPDATE:

there's a few things i missed (to much snippets were copy & pasted...). first, the deployer should not dependOn(backend,frontend) at all. just aggregate. moreover, these artifacts (backend & frontend) should not be visible on deployer's classpath at all. so this line:

classpathTypes :=  classpathTypes.value + "zip"

is unneeded. also, the code to transform the mappings in oneJar in the deployer/build.sbt file, could be much more simple. only need to take care of resources. and finally, the commented out lines, should actually be:

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"}

that's pretty much it.

gilad hoch
  • 2,846
  • 2
  • 33
  • 57

0 Answers0