Skip to content

Commit

Permalink
Ensure that dependent projects (and our own) jars are created when
Browse files Browse the repository at this point in the history
making a JavaApp distribution.  We use the dependency-classpath in Runtime
to determine classpath ordering, but we pull jars directly from the
packagedArtifacts keys of dependent projects.  Uses a bit of
advanced sbt hackery.

Fixes #19

However, this introduces an error in the scripts I'm looking into.
Will squash this commit with further fixes.
  • Loading branch information
jsuereth committed Aug 21, 2013
1 parent 63232b0 commit e7272a7
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/main/scala/com/typesafe/sbt/packager/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ object Keys extends linux.Keys
val bashScriptDefines = TaskKey[Seq[String]]("bashScriptDefines", "A list of definitions that should be written to the bash file template.")
val bashScriptExtraDefines = TaskKey[Seq[String]]("bashScriptExtraDefines", "A list of extra definitions that should be written to the bash file template.")
val scriptClasspathOrdering = TaskKey[Seq[(File, String)]]("scriptClasspathOrdering", "The order of the classpath used at runtime for the bat/bash scripts.")
val projectDependencyArtifacts = TaskKey[Seq[Attributed[File]]]("projectDependencyArtifacts", "The set of exported artifacts from our dependent projects.")
val scriptClasspath = TaskKey[Seq[String]]("scriptClasspath", "A list of relative filenames (to the lib/ folder in the distribution) of what to include on the classpath.")
val makeBatScript = TaskKey[Option[File]]("makeBatScript", "Creates or discovers the bat script used by this project.")
val batScriptReplacements = TaskKey[Seq[(String,String)]]("batScriptReplacements",
Expand Down
109 changes: 91 additions & 18 deletions src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package archetypes

import Keys._
import sbt._
import sbt.Project.Initialize
import sbt.Keys.{mappings, target, name, mainClass, normalizedName}
import linux.LinuxPackageMapping
import SbtNativePackager._
Expand All @@ -22,10 +23,15 @@ object JavaAppPackaging {
// Here we record the classpath as it's added to the mappings separately, so
// we can use its order to generate the bash/bat scripts.
scriptClasspathOrdering := Nil,
scriptClasspathOrdering <+= (Keys.packageBin in Compile) map { jar =>
jar -> ("lib/" + jar.getName)
// Note: This is sometimes on the classpath via depnedencyClasspath in Runtime.
// We need to figure out why sometimes the Attributed[File] is corrrectly configured
// and sometimes not.
scriptClasspathOrdering <+= (Keys.packageBin in Compile, Keys.projectID, Keys.artifact in Compile in Keys.packageBin) map { (jar, id, art) =>
jar -> ("lib/" + makeJarName(id.organization,id.name,id.revision, art.name))
},
scriptClasspathOrdering <++= (Keys.dependencyClasspath in Runtime) map universalDepMappings,
projectDependencyArtifacts <<= findDependencyProjectArtifacts,
//scriptClasspathOrdering <++= projectDependencyMappings,
scriptClasspathOrdering <++= (Keys.dependencyClasspath in Runtime, projectDependencyArtifacts) map universalDepMappings,
mappings in Universal <++= scriptClasspathOrdering,
scriptClasspath <<= scriptClasspathOrdering map makeRelativeClasspathNames,
bashScriptExtraDefines := Nil,
Expand Down Expand Up @@ -86,24 +92,91 @@ object JavaAppPackaging {
Some(script)
}

// Constructs a jar name from components...(ModuleID/Artifact)
def makeJarName(org: String, name: String, revision: String, artifactName: String): String =
(org + "." +
name + "-" +
Option(artifactName.replace(name, "")).filterNot(_.isEmpty).map(_ + "-").getOrElse("") +
revision + ".jar")

// Determines a nicer filename for an attributed jar file, using the
// ivy metadata if available.
def getJarFullFilename(dep: Attributed[File]): String = {
val filename: Option[String] = for {
module <- dep.metadata.get(AttributeKey[ModuleID]("module-id"))
artifact <- dep.metadata.get(AttributeKey[Artifact]("artifact"))
} yield makeJarName(module.organization, module.name, module.revision, artifact.name)
filename.getOrElse(dep.data.getName)
}

// Here we grab the dependencies...
def dependencyProjectRefs(build: sbt.BuildDependencies, thisProject: ProjectRef): Seq[ProjectRef] =
build.classpathTransitive.get(thisProject).getOrElse(Nil)

def filterArtifacts(artifacts: Seq[(Artifact, File)], config: Option[String]): Seq[(Artifact, File)] =
for {
(art, file) <- artifacts
// TODO - Default to compile or default?
if art.configurations.exists(_.name == config.getOrElse("default"))
} yield art -> file

def extractArtifacts(stateTask: Task[State], ref: ProjectRef): Task[Seq[Attributed[File]]] =
stateTask flatMap { state =>
val extracted = Project extract state
// TODO - Is this correct?
val module = extracted.get(sbt.Keys.projectID in ref)
val artifactTask = extracted get (sbt.Keys.packagedArtifacts in ref)
for {
arts <- artifactTask
} yield {
for {
(art, file) <- arts.toSeq // TODO -Filter!
} yield {
sbt.Attributed.blank(file).
put(sbt.Keys.moduleID.key, module).
put(sbt.Keys.artifact.key, art)
}
}
}

def findDependencyProjectArtifacts: Initialize[Task[Seq[Attributed[File]]]] =
(sbt.Keys.buildDependencies, sbt.Keys.thisProjectRef, sbt.Keys.state) apply { (build, thisProject, stateTask) =>
val refs = thisProject +: dependencyProjectRefs(build, thisProject)
// Dynamic lookup of dependencies...
val artTasks = (refs) map { ref => extractArtifacts(stateTask, ref) }
val allArtifactsTask: Task[Seq[Attributed[File]]] =
artTasks.fold[Task[Seq[Attributed[File]]]](task(Nil)) { (previous, next) =>
for {
p <- previous
n <- next
} yield (p ++ n).distinct
}
allArtifactsTask
}

def findRealDep(dep: Attributed[File], projectArts: Seq[Attributed[File]]): Option[Attributed[File]] = {
if(dep.data.isFile) Some(dep)
else {
projectArts.find { art =>
// TODO - Why is the module not showing up for project deps?
//(art.get(sbt.Keys.moduleID.key) == dep.get(sbt.Keys.moduleID.key)) &&
((art.get(sbt.Keys.artifact.key), dep.get(sbt.Keys.artifact.key))) match {
case (Some(l), Some(r)) =>
// TODO - extra attributes and stuff for comparison?
// seems to break stuff if we do...
(l.name == r.name)
case _ => false
}
}
}
}

// Converts a managed classpath into a set of lib mappings.
def universalDepMappings(deps: Seq[Attributed[File]]): Seq[(File,String)] =
def universalDepMappings(deps: Seq[Attributed[File]], projectArts: Seq[Attributed[File]]): Seq[(File,String)] =
for {
dep <- deps
file = dep.data
if file.isFile
// TODO - Figure out what to do with jar files.
realDep <- findRealDep(dep, projectArts)
} yield {
val filename: Option[String] = for {
module <- dep.metadata.get(AttributeKey[ModuleID]("module-id"))
artifact <- dep.metadata.get(AttributeKey[Artifact]("artifact"))
} yield {
module.organization + "." +
module.name + "-" +
Option(artifact.name.replace(module.name, "")).filterNot(_.isEmpty).map(_ + "-").getOrElse("") +
module.revision + ".jar"
}

dep.data -> ("lib/" + filename.getOrElse(file.getName))
realDep.data-> ("lib/"+getJarFullFilename(realDep))
}
}
10 changes: 9 additions & 1 deletion src/sbt-test/debian/java-app-archetype/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ debianPackageRecommends in Debian += "git"

TaskKey[Unit]("check-script") <<= (stagingDirectory in Universal, name, streams) map { (dir, name, streams) =>
val script = dir / "bin" / name
val cmd = "bash " + script.getAbsolutePath
System.out.synchronized {
System.err.println("---SCIRPT---")
val scriptContents = IO.read(script)
System.err.println(scriptContents)
System.err.println("---END SCIRPT---")
for(file <- (dir.***).get)
System.err.println("\t"+file)
}
val cmd = "bash " + script.getAbsolutePath + " -d"
val result =
Process(cmd) ! streams.log match {
case 0 => ()
Expand Down

0 comments on commit e7272a7

Please sign in to comment.