Skip to content

Instantly share code, notes, and snippets.

@4lex1v
Last active December 30, 2015 18:29
Show Gist options
  • Save 4lex1v/7868356 to your computer and use it in GitHub Desktop.
Save 4lex1v/7868356 to your computer and use it in GitHub Desktop.
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