From 63232b0a4a418a4313c7e5489ce43757e41d2c3e Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 21 Aug 2013 11:46:43 -0400 Subject: [PATCH 1/2] Adding test for bug #19 --- .../project/Build.scala | 49 +++++++++++++++++++ .../project/plugins.sbt | 1 + .../mutliproject-java-app-archetype/test | 7 +++ 3 files changed, 57 insertions(+) create mode 100644 src/sbt-test/universal/mutliproject-java-app-archetype/project/Build.scala create mode 100644 src/sbt-test/universal/mutliproject-java-app-archetype/project/plugins.sbt create mode 100644 src/sbt-test/universal/mutliproject-java-app-archetype/test diff --git a/src/sbt-test/universal/mutliproject-java-app-archetype/project/Build.scala b/src/sbt-test/universal/mutliproject-java-app-archetype/project/Build.scala new file mode 100644 index 000000000..2fe3357f6 --- /dev/null +++ b/src/sbt-test/universal/mutliproject-java-app-archetype/project/Build.scala @@ -0,0 +1,49 @@ +import sbt._ +import Keys._ +import com.typesafe.sbt.SbtNativePackager._ + +object MutliBuild extends Build { + + val appName = "play-bug-1499" + val appVersion = "1.0" + + val mySettings: Seq[Setting[_]] = + packageArchetype.java_application ++ + Seq( + organization := "org.test", + version := appVersion, + TaskKey[Unit]("show-files") <<= (name, target, streams) map { (n, t, s) => + System.out.synchronized { + println("Files in ["+n+"]") + val files = (t / "universal/stage").***.get + files foreach println + } + } + ) + + + lazy val common = ( + Project(appName + "-common", file("module/common")) + settings(mySettings:_*) + ) + + lazy val foo = ( + Project(appName + "-foo", file("module/foo")) + settings(mySettings:_*) + dependsOn(common) + ) + + lazy val bar = ( + Project(appName + "-bar", file("module/bar")) + settings(mySettings:_*) + dependsOn(common) + ) + + lazy val aaMain = ( + Project(appName + "-main", file(".")) + settings(mySettings:_*) + dependsOn(common,foo,bar) + aggregate(foo,bar) + ) + +} \ No newline at end of file diff --git a/src/sbt-test/universal/mutliproject-java-app-archetype/project/plugins.sbt b/src/sbt-test/universal/mutliproject-java-app-archetype/project/plugins.sbt new file mode 100644 index 000000000..b53de154c --- /dev/null +++ b/src/sbt-test/universal/mutliproject-java-app-archetype/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version")) diff --git a/src/sbt-test/universal/mutliproject-java-app-archetype/test b/src/sbt-test/universal/mutliproject-java-app-archetype/test new file mode 100644 index 000000000..e62a87154 --- /dev/null +++ b/src/sbt-test/universal/mutliproject-java-app-archetype/test @@ -0,0 +1,7 @@ +# Run the staging and check the script. +> stage +> show-files +$ exists target/universal/stage/lib/org.test.play-bug-1499-foo-1.0.jar +$ exists target/universal/stage/lib/org.test.play-bug-1499-common-1.0.jar +$ exists target/universal/stage/lib/org.test.play-bug-1499-bar-1.0.jar +$ exists target/universal/stage/lib/org.test.play-bug-1499-main-1.0.jar \ No newline at end of file From e7272a7e75318165f5c9a1ccc7a86000a91a52ae Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 21 Aug 2013 11:48:47 -0400 Subject: [PATCH 2/2] Ensure that dependent projects (and our own) jars are created when 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. --- .../com/typesafe/sbt/packager/Keys.scala | 1 + .../sbt/packager/archetypes/JavaApp.scala | 109 +++++++++++++++--- .../debian/java-app-archetype/build.sbt | 10 +- 3 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/main/scala/com/typesafe/sbt/packager/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/Keys.scala index 06c08994c..dc063c822 100644 --- a/src/main/scala/com/typesafe/sbt/packager/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/Keys.scala @@ -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", diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala index 728f27085..938e972db 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala @@ -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._ @@ -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, @@ -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)) } } \ No newline at end of file diff --git a/src/sbt-test/debian/java-app-archetype/build.sbt b/src/sbt-test/debian/java-app-archetype/build.sbt index 35f3ce61a..c8c01ef1b 100644 --- a/src/sbt-test/debian/java-app-archetype/build.sbt +++ b/src/sbt-test/debian/java-app-archetype/build.sbt @@ -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 => ()