Last active
December 30, 2015 18:29
-
-
Save 4lex1v/7868356 to your computer and use it in GitHub Desktop.
Sbt distribution for SO question http://stackoverflow.com/questions/20455509/custom-deployment-with-sbt, code is based on https://github.com/akka/akka/blob/master/akka-sbt-plugin/src/main/scala/AkkaKernelPlugin.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sbt._ | |
import sbt.Keys._ | |
import sbt.BuildStructure | |
import sbt.classpath.ClasspathUtilities | |
import sbt.Def.Initialize | |
import sbt.CommandUtil._ | |
import java.io.File | |
object Distribution extends Plugin { | |
case class DistConfig( | |
outputDirectory: File, | |
configSourceDirs: Seq[File], | |
distJvmOptions: String, | |
distMainClass: String, | |
libFilter: File ⇒ Boolean, | |
additionalLibs: Seq[File]) | |
val Dist = config("dist") extend Runtime | |
val dist = TaskKey[File]("dist", "Builds an application distribution folder directory") | |
val distClean = TaskKey[Unit]("clean", "Removes Akka microkernel directory") | |
val outputDirectory = SettingKey[File]("output-directory") | |
val configSourceDirs = TaskKey[Seq[File]]("config-source-directories", "Configuration files are copied from these directories") | |
val distJvmOptions = SettingKey[String]("app-jvm-options", "JVM parameters to use in start script") | |
val distMainClass = TaskKey[String]("app-main-class", "main class to use in start script, defaults to akka.kernel.Main to load an akka.kernel.Bootable") | |
val zipDist = TaskKey[File]("zip-dist", "Creates a distributable zip archive") | |
val libFilter = SettingKey[File ⇒ Boolean]("lib-filter", "Filter of dependency jar files") | |
val additionalLibs = TaskKey[Seq[File]]("additional-libs", "Additional dependency jar files") | |
val distConfig = TaskKey[DistConfig]("dist-config") | |
val distNeedsPackageBin = dist <<= dist.dependsOn(packageBin in Compile) | |
lazy val distSettings: Seq[Setting[_]] = inConfig(Dist) { | |
Seq( | |
dist <<= packageBin, | |
packageBin <<= distTask, | |
distClean <<= distCleanTask, | |
dependencyClasspath <<= (dependencyClasspath in Runtime), | |
unmanagedResourceDirectories <<= (unmanagedResourceDirectories in Runtime), | |
outputDirectory <<= target { _ / "app" }, | |
configSourceDirs <<= defaultConfigSourceDirs, | |
distJvmOptions := "-Xms1024M -Xmx1024M -Xss1M -XX:MaxPermSize=256M -XX:+UseParallelGC", | |
distMainClass := (mainClass in Compile).value.getOrElse(""), | |
libFilter := { f ⇒ true }, | |
additionalLibs <<= defaultAdditionalLibs, | |
distConfig <<= (outputDirectory, configSourceDirs, distJvmOptions, distMainClass, libFilter, additionalLibs) map DistConfig) | |
} ++ Seq(dist <<= (dist in Dist), distNeedsPackageBin, zipDist <<= defaultZipDist) | |
private def defaultZipDist: Initialize[Task[File]] = { | |
(dist, name, outputDirectory in Dist) map { (_, mname, outDir) => | |
val mapping = (outDir ***) x rebase(outDir, "application") | |
val zip = outDir.getParentFile / s"$mname.zip" | |
IO.zip(mapping, zip) | |
zip | |
} | |
} | |
private def distTask: Initialize[Task[File]] = | |
(thisProject, distConfig, sourceDirectory, crossTarget, dependencyClasspath, allDependencies, buildStructure, state) map { | |
(project, conf, src, tgt, cp, allDeps, buildStruct, st) ⇒ | |
val log = st.log | |
val distBinPath = conf.outputDirectory / "bin" | |
val distConfigPath = conf.outputDirectory / "config" | |
val distDeployPath = conf.outputDirectory / "deploy" | |
val distLibPath = conf.outputDirectory / "lib" | |
val subProjectDependencies: Set[SubProjectInfo] = allSubProjectDependencies(project, buildStruct, st) | |
log.info("Creating distribution %s ..." format conf.outputDirectory) | |
IO.createDirectory(conf.outputDirectory) | |
Scripts(conf.distJvmOptions, conf.distMainClass).writeScripts(distBinPath) | |
copyDirectories(conf.configSourceDirs, distConfigPath) | |
copyJars(tgt, distDeployPath) | |
copyFiles(libFiles(cp, conf.libFilter), distLibPath) | |
copyFiles(conf.additionalLibs, distLibPath) | |
for (subProjectDependency ← subProjectDependencies) { | |
val subTarget = subProjectDependency.target | |
EvaluateTask(buildStruct, packageBin in Compile, st, subProjectDependency.projectRef) | |
copyJars(subTarget, distLibPath) | |
} | |
log.info("Distribution created.") | |
conf.outputDirectory | |
} | |
private def distCleanTask: Initialize[Task[Unit]] = | |
(outputDirectory, allDependencies, streams) map { (outDir, deps, s) ⇒ | |
val log = s.log | |
log.info("Cleaning " + outDir) | |
IO.delete(outDir) | |
} | |
private def defaultConfigSourceDirs = (sourceDirectory, unmanagedResourceDirectories) map { (src, resources) ⇒ | |
Seq(src / "config", src / "main" / "config") ++ resources | |
} | |
private def defaultAdditionalLibs = libraryDependencies map { (libs) ⇒ | |
Seq.empty[File] | |
} | |
private case class Scripts(jvmOptions: String, mainClass: String) { | |
def writeScripts(to: File) = { | |
scripts.map { script ⇒ | |
val target = new File(to, script.name) | |
IO.write(target, script.contents) | |
setExecutable(target, script.executable) | |
}.foldLeft(None: Option[String])(_ orElse _) | |
} | |
private case class DistScript(name: String, contents: String, executable: Boolean) | |
private def scripts = Set(DistScript("start", distShScript, true)) | |
private def distShScript = | |
("#!/bin/sh\n\n" + | |
"PROJECT_HOME=\"$(cd \"$(cd \"$(dirname \"$0\")\"; pwd -P)\"/..; pwd)\"\n" + | |
"PROJECT_CLASSPATH=\"$PROJECT_HOME/config:$PROJECT_HOME/deploy/*:$PROJECT_HOME/lib/*\"\n" + | |
"JAVA_OPTS=\"%s\"\n\n" + | |
"java $JAVA_OPTS -cp \"$PROJECT_CLASSPATH\" %s\n").format(jvmOptions, mainClass) | |
private def setExecutable(target: File, executable: Boolean): Option[String] = { | |
val success = target.setExecutable(executable, false) | |
if (success) None else Some("Couldn't set permissions of " + target) | |
} | |
} | |
private def copyDirectories(fromDirs: Seq[File], to: File) = { | |
IO.createDirectory(to) | |
for (from ← fromDirs) { | |
IO.copyDirectory(from, to) | |
} | |
} | |
private def copyJars(fromDir: File, toDir: File) = { | |
val jarFiles = fromDir.listFiles.filter(f ⇒ | |
f.isFile && | |
f.name.endsWith(".jar") && | |
!f.name.contains("-sources") && | |
!f.name.contains("-docs")) | |
copyFiles(jarFiles, toDir) | |
} | |
private def copyFiles(files: Seq[File], toDir: File) = { | |
for (f ← files) { | |
IO.copyFile(f, new File(toDir, f.getName)) | |
} | |
} | |
private def libFiles(classpath: Classpath, libFilter: File ⇒ Boolean): Seq[File] = { | |
val (libs, directories) = classpath.map(_.data).partition(ClasspathUtilities.isArchive) | |
libs.map(_.asFile).filter(libFilter) | |
} | |
private def includeProject(project: ResolvedProject, parent: ResolvedProject): Boolean = { | |
parent.uses.exists { | |
case ProjectRef(uri, id) ⇒ id == project.id | |
case _ ⇒ false | |
} | |
} | |
private def allSubProjectDependencies(project: ResolvedProject, buildStruct: BuildStructure, state: State): Set[SubProjectInfo] = { | |
val buildUnit = buildStruct.units(buildStruct.root) | |
val uri = buildStruct.root | |
val allProjects = buildUnit.defined.map { | |
case (id, proj) ⇒ ProjectRef(uri, id) -> proj | |
} | |
val subProjects: Seq[SubProjectInfo] = allProjects.collect { | |
case (projRef, proj) if includeProject(proj, project) ⇒ projectInfo(projRef, proj, buildStruct, state, allProjects) | |
}.toList | |
val allSubProjects = subProjects.map(_.recursiveSubProjects).flatten.toSet | |
allSubProjects | |
} | |
private def projectInfo(projectRef: ProjectRef, project: ResolvedProject, buildStruct: BuildStructure, state: State, | |
allProjects: Map[ProjectRef, ResolvedProject]): SubProjectInfo = { | |
def optionalSetting[A](key: SettingKey[A]) = key in projectRef get buildStruct.data | |
def setting[A](key: SettingKey[A], errorMessage: ⇒ String) = { | |
optionalSetting(key) getOrElse { | |
state.log.error(errorMessage) | |
throw new IllegalArgumentException() | |
} | |
} | |
val subProjects = allProjects.collect { | |
case (projRef, proj) if includeProject(proj, project) ⇒ projectInfo(projRef, proj, buildStruct, state, allProjects) | |
}.toList | |
val target = setting(Keys.crossTarget, "Missing crossTarget directory") | |
SubProjectInfo(projectRef, target, subProjects) | |
} | |
private case class SubProjectInfo(projectRef: ProjectRef, target: File, subProjects: Seq[SubProjectInfo]) { | |
def recursiveSubProjects: Set[SubProjectInfo] = { | |
val flatSubProjects = for { | |
x ← subProjects | |
y ← x.recursiveSubProjects | |
} yield y | |
flatSubProjects.toSet + this | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment