blob: cd7029721d044ec7e1b6e35ae14a68900e9db05c [file] [log] [blame]
import sbt._
import Keys._
import scala.language.existentials
import com.typesafe.sbt.SbtNativePackager._
import NativePackagerKeys._
import com.typesafe.sbt.SbtLicenseReport.autoImportImpl._
import com.typesafe.sbt.license.LicenseCategory
import com.typesafe.sbt.license.LicenseInfo
import com.typesafe.sbt.license.DepModuleInfo
import com.typesafe.sbt.license.Html
import com.typesafe.sbt.pgp.PgpSettings._
object DaffodilBuild extends Build {
var s = Defaults.defaultSettings
lazy val nopub = Seq(publish := {}, publishLocal := {}, packagedArtifacts := Map.empty )
var cliOnlySettings = packageArchetype.java_application
// sbt uses reflection to find suprojects. we need to also add the projects
// in daffodil-extra, which can't be detected via reflection
override def projects: Seq[Project] = super.projects ++ extraProjects
// daffodil projects
lazy val root = {
val r = Project(id = "daffodil", base = file("."), settings = s ++ nopub)
.configs(DebugTest)
.configs(NewTest)
.configs(CliTest)
.aggregate(propgen, lib, io, core, tdml, testIBM1, cli, test, examples)
extraProjects.foldLeft(r) { (r, p) => r.aggregate(p) }
}
lazy val propgen = Project(id = "daffodil-propgen", base = file("daffodil-propgen"), settings = s ++ nopub)
.configs(DebugTest)
.configs(NewTest)
lazy val lib = Project(id = "daffodil-lib", base = file("daffodil-lib"), settings = s ++ propgenSettings ++ schemasgenSettings ++ managedgenSettings)
.configs(DebugTest)
.configs(NewTest)
lazy val io = Project(id = "daffodil-io", base = file("daffodil-io"), settings = s)
.configs(DebugTest)
.configs(NewTest)
.dependsOn(lib)
lazy val core = Project(id = "daffodil-core", base = file("daffodil-core"), settings = s)
.configs(DebugTest)
.configs(NewTest)
.dependsOn(io)
//
// Keep as illustration of how to make test code depend on another module's
// test code. Use case is test utility libraries in one module that want
// to be shared, but are only used for testing.
//
// lazy val runtime1 = Project(id = "daffodil-runtime1", base = file("daffodil-runtime1"), settings = s)
// .configs(DebugTest)
// .configs(NewTest)
// .dependsOn(core % "compile->compile;test->test") // // test in core has utilities that test in runtime1 needs.
//
lazy val tdml = Project(id = "daffodil-tdml", base = file("daffodil-tdml"), settings = s)
.configs(DebugTest)
.configs(NewTest)
.dependsOn(core)
lazy val cli = Project(id = "daffodil-cli", base = file("daffodil-cli"), settings = s ++ cliOnlySettings)
.configs(DebugTest)
.configs(NewTest)
.configs(CliTest)
.dependsOn(tdml)
lazy val test = Project(id = "daffodil-test", base = file("daffodil-test"), settings = s ++ nopub)
.configs(DebugTest)
.configs(NewTest)
.dependsOn(tdml)
.dependsOn(core % "test->test") // need test utilities in core's test dirs
lazy val examples = Project(id = "daffodil-examples", base = file("daffodil-examples"), settings = s ++ nopub)
.configs(DebugTest)
.configs(NewTest)
.dependsOn(tdml)
lazy val testIBM1 = Project(id = "daffodil-test-ibm1", base = file("daffodil-test-ibm1"), settings = s ++ nopub)
.configs(DebugTest)
.configs(NewTest)
.dependsOn(tdml)
// any directories in daffodil-extra are treated as subprojects. this is
// useful for symlinking tests that should not be committed to the git repo
// but still be compiled/tested as part of daffodil
lazy val extrasDir = new File("daffodil-extra")
lazy val extraProjects = extrasDir.listFiles.filter { _.isDirectory }.map { dir =>
Project(id = dir.name, base = dir, settings = s ++ nopub)
.configs(DebugTest)
.configs(NewTest)
.dependsOn(tdml)
}
//set up 'sbt stage' as a dependency
lazy val cliTestTask = Keys.test in CliTest
lazy val cliTestOnlyTask = Keys.testOnly in CliTest
lazy val cliTestQuickTask = Keys.testQuick in CliTest
lazy val stageTask = stage in Compile
lazy val stageTaskSettings = Seq(
(cliTestTask <<= cliTestTask.dependsOn(stageTask)),
(cliTestOnlyTask <<= cliTestOnlyTask.dependsOn(stageTask)),
(cliTestQuickTask <<= cliTestQuickTask.dependsOn(stageTask)),
(cliTask <<= cliTask.dependsOn(stageTask))
)
cliOnlySettings ++= stageTaskSettings
val managedGenerator = TaskKey[Unit]("gen-managed", "Generate managed sources and resources")
lazy val managedgenSettings = managedGenerator <<= Seq(propertyGenerator in Compile, schemasGenerator in Compile).dependOn
val propertyGenerator = TaskKey[Seq[File]]("gen-props", "Generate properties scala source")
lazy val propgenSettings = Seq(
sourceGenerators in Compile <+= (propertyGenerator in Compile),
propertyGenerator in Compile <<=
(sourceManaged in Compile, dependencyClasspath in Runtime in propgen, sources in Compile in propgen, resources in Compile in propgen, streams in propgen) map {
(outdir, cp, inSrc, inRSrc, stream) => {
// FileFunction.cached will only run the property generator if any
// source or resources in propgen subproject changed
val filesToWatch = (inSrc ++ inRSrc).toSet
val cachedFun = FileFunction.cached(stream.cacheDirectory / "propgen", FilesInfo.lastModified, FilesInfo.exists) {
(in: Set[File]) => runPropertyGenerator(outdir, cp.files, stream.log)
}
cachedFun(filesToWatch).toSeq
}
}
)
def runPropertyGenerator(outdir: File, cp: Seq[File], log: Logger): Set[File] = {
val mainClass = "edu.illinois.ncsa.daffodil.propGen.PropertyGenerator"
val out = new java.io.ByteArrayOutputStream()
val forkOpts = new ForkOptions(None, Some(CustomOutput(out)), cp, None, Nil, false)
val ret = new Fork("java", Some(mainClass)).fork(forkOpts, Seq(outdir.toString)).exitValue()
if (ret != 0) {
sys.error("Failed to generate code")
}
val in = new java.io.InputStreamReader(new java.io.ByteArrayInputStream(out.toByteArray))
val bin = new java.io.BufferedReader(in)
val iterator = Iterator.continually(bin.readLine()).takeWhile(_ != null)
val files = iterator.map { f =>
log.info("Generated %s".format(f))
new File(f)
}.toSet
files
}
val schemasGenerator = TaskKey[Seq[File]]("gen-schemas", "Generated DFDL schemas")
lazy val schemasgenSettings = Seq(
resourceGenerators in Compile <+= (schemasGenerator in Compile),
schemasGenerator in Compile <<=
(resourceManaged in Compile, resources in Compile in propgen, streams in propgen) map {
(outdir, inRSrc, stream) => {
// FileFunction.cached will only run the schema generator if any
// resources in propgen subproject changed
val filesToWatch = inRSrc.toSet
val cachedFun = FileFunction.cached(stream.cacheDirectory / "schemasgen", FilesInfo.lastModified, FilesInfo.exists) {
(in: Set[File]) => copyResources(outdir, inRSrc.toSet, stream.log)
}
cachedFun(filesToWatch).toSeq
}
}
)
def copyResources(outdir: File, inRSrc: Set[File], log: Logger): Set[File] = {
val dfdlSchemas = inRSrc.filter { f => f.getName.matches("DFDL_part.*\\.xsd") }
val managed_resources = dfdlSchemas.map { in =>
val out = outdir / "xsd" / in.getName
IO.copyFile(in, out)
log.info("Generated %s".format(out))
out
}
managed_resources
}
// modify the managed source and resource directories so that any generated code can be more easily included in IDE's
s ++= Seq(sourceManaged <<= baseDirectory(_ / "src_managed"))
s ++= Seq(resourceManaged <<= baseDirectory(_ / "resource_managed"))
// modify the managed libraries directory to be lib/jars/ instead of lib,
// allowing us to manage sources/docs in lib/srcs and lib/docs without them
// being included as a dependency
s ++= Seq(unmanagedBase <<= baseDirectory(_ / "lib" / "jars"))
// creates 'sbt debug:*' tasks, using src/test/scala-debug as the source directory
lazy val DebugTest = config("debug") extend(Test)
lazy val debugSettings: Seq[Setting[_]] = inConfig(DebugTest)(Defaults.testSettings ++ Seq(
sourceDirectory <<= baseDirectory(_ / "src" / "test"),
scalaSource <<= sourceDirectory(_ / "scala-debug"),
javaSource <<= sourceDirectory(_ / "java-debug"),
exportJars := false,
publishArtifact := false
))
s ++= Seq(debugSettings : _*)
// creates 'sbt debug' task, which is essentially an alias for 'sbt debug:test'
lazy val debugTask = TaskKey[Unit]("debug", "Executes all debug tests")
lazy val debugTaskSettings = debugTask <<= (executeTests in DebugTest, streams in DebugTest, resolvedScoped in DebugTest, state in DebugTest) map {
(results, s, scoped, state) => {
val display = Project.showContextKey(state)
Tests.showResults(s.log, results, "No tests to run for " + display(scoped))
}
}
s ++= Seq(debugTaskSettings)
// creates 'sbt new:*' tasks, using src/test/scala-new as the source directory
lazy val NewTest = config("new") extend(Test)
lazy val newSettings: Seq[Setting[_]] = inConfig(NewTest)(Defaults.testSettings ++ Seq(
sourceDirectory <<= baseDirectory(_ / "src" / "test"),
scalaSource <<= sourceDirectory(_ / "scala-new"),
javaSource <<= sourceDirectory(_ / "java-new"),
exportJars := false,
publishArtifact := false
))
s ++= Seq(newSettings : _*)
// creates 'sbt new' task, which is essentially an alias for 'sbt new:test'
lazy val newTask = TaskKey[Unit]("new", "Executes all new tests")
lazy val newTaskSettings = newTask <<= (executeTests in NewTest, streams in NewTest, resolvedScoped in NewTest, state in NewTest) map {
(results, s, scoped, state) => {
val display = Project.showContextKey(state)
Tests.showResults(s.log, results, "No tests to run for " + display(scoped))
}
}
s ++= Seq(newTaskSettings)
// add scala-new as a source test directory for the 'sbt test' commands
lazy val buildNewWithTestSettings = unmanagedSourceDirectories in Test <++= baseDirectory { base =>
Seq(base / "src/test/scala-new")
}
s ++= Seq(buildNewWithTestSettings)
// creates 'sbt cli:*' tasks, using src/test/scala-cli as the source directory
lazy val CliTest = config("cli") extend(Test)
lazy val cliSettings: Seq[Setting[_]] = inConfig(CliTest)(Defaults.testSettings ++ Seq(
sourceDirectory <<= baseDirectory(_ / "src" / "test"),
scalaSource <<= sourceDirectory(_ / "scala-cli"),
javaSource <<= sourceDirectory(_ / "java-cli"),
exportJars := false,
publishArtifact := false
))
s ++= Seq(cliSettings : _*)
// creates 'sbt cli' task, which is essentially an alias for 'sbt cli:test'
lazy val cliTask = TaskKey[Unit]("cli", "Executes all CLI tests")
lazy val cliTaskSettings = cliTask <<= (executeTests in CliTest, streams in CliTest, resolvedScoped in CliTest, state in CliTest) map {
(results, s, scoped, state) => {
val display = Project.showContextKey(state)
Tests.showResults(s.log, results, "No tests to run for " + display(scoped))
}
}
s ++= Seq(cliTaskSettings)
// license report configuration
val licenseSettings = Seq(
licenseReportTitle := "Daffodil Licenses",
licenseConfigurations := Set("compile"),
licenseSelection := Seq(LicenseCategory("NCSA"), LicenseCategory("ICU")) ++ LicenseCategory.all,
licenseOverrides := {
case DepModuleInfo("commons-io", "commons-io", _) => LicenseInfo(LicenseCategory.Apache, "The Apache Software License, Version 2.0", "http://www.apache.org/licenses/LICENSE-2.0.html")
case DepModuleInfo("net.sf.expectit", "expectit-core", _) => LicenseInfo(LicenseCategory.Apache, "The Apache Software License, Version 2.0", "http://www.apache.org/licenses/LICENSE-2.0.html")
case DepModuleInfo("xml-resolver", "xml-resolver", _) => LicenseInfo(LicenseCategory.Apache, "The Apache Software License, Version 2.0", "http://www.apache.org/licenses/LICENSE-2.0.html")
case DepModuleInfo("org.scala-tools.testing", "test-interface", _) => LicenseInfo(LicenseCategory.BSD, "BSD", "https://github.com/sbt/test-interface/blob/master/LICENSE")
case DepModuleInfo("org.hamcrest", "hamcrest-core", _) => LicenseInfo(LicenseCategory.BSD, "BSD", "https://github.com/hamcrest/JavaHamcrest/blob/master/LICENSE.txt")
},
licenseFilter := {
case LicenseCategory("NCSA", _) => false
case _ => true
},
licenseReportMakeHeader := {
case Html => Html.header1(licenseReportTitle.value) + "<p>Daffodil is licensed under the <a href='http://opensource.org/licenses/NCSA'>University of Illinois/NCSA Open Source License</a>.</p><p>Below are the libraries that Daffodil depends on and their licenses.<br></p>"
case l => l.header1(licenseReportTitle.value)
}
)
cliOnlySettings ++= licenseSettings
// native package configuration
val exampleDirMappings = {
// this recursively gathers all files in the daffodil-examples resources
// directory and creates a mapping to the relative location in the
// 'examples' directory which is packaged in zip/tars
val examplesDir = file("daffodil-examples/src/test/resources/edu/illinois/ncsa/daffodil/")
val allFiles = examplesDir.***
val mappings = allFiles.pair { f: File =>
val relative = f.relativeTo(examplesDir.getParentFile).get.toString
val exRelative = relative.replaceFirst("^daffodil", "examples")
Some(exRelative)
}
mappings
}
val packageSettings = Seq(
packageName := "daffodil",
mappings in Universal ++= exampleDirMappings,
mappings in Universal += dumpLicenseReport.value / (licenseReportTitle.value + ".html") -> "LICENSES.html",
mappings in Universal += baseDirectory.value / "README" -> "README"
)
cliOnlySettings ++= packageSettings
// test report plugin configuration
lazy val testReportSettings = testListeners <+= (crossTarget) map {
ct => new eu.henkelmann.sbt.JUnitXmlTestsListener(ct.getPath)
}
s ++= Seq(testReportSettings)
def exec(cmd: String): Seq[String] = {
val r = java.lang.Runtime.getRuntime()
val p = r.exec(cmd)
p.waitFor()
val ret = p.exitValue()
if (ret != 0) {
sys.error("Command failed: " + cmd)
}
val is = p.getInputStream
val res = scala.io.Source.fromInputStream(is).getLines()
res.toSeq
}
// get the version from the latest tag
s ++= Seq(version := {
val describe = exec("git describe --long HEAD")
assert(describe.length == 1)
val DescribeRegex = """^(.+)-(.+)-(.+)$""".r
val res = describe(0) match {
case DescribeRegex(taggedVersion, "0", hash) => {
// we are on a tag, build a tag release
val status = exec("git status --porcelain")
if (status.length > 0) {
taggedVersion + "-SNAPSHOT"
} else {
taggedVersion
}
}
case DescribeRegex(version, _, hash) => {
// not on a tag
// get the current branch
val branch = exec("git rev-parse --symbolic-full-name HEAD")
assert(branch.length == 1)
val VersionBranchRegex = """^refs/heads/(\d+\.\d+\.\d+)$""".r
branch(0) match {
case "HEAD" => {
// not on the tip of a branch
"0.0.0-SNAPSHOT"
}
case VersionBranchRegex(versionBranch) => {
// we are developing on a version branch, create a snapshot
versionBranch + "-SNAPSHOT"
}
case branch => {
// not on a version branch (e.g. a review branch), try to figure
// out the tracking branch
val trackingBranch = exec("git for-each-ref --format=%(upstream:short) " + branch)
assert(trackingBranch.length == 1)
val TrackingBranchRegex = """^[^/]+/(\d+\.\d+\.\d+)$""".r
trackingBranch(0) match {
case TrackingBranchRegex(trackingVersion) => {
trackingVersion + "-SNAPSHOT"
}
case _ => {
// no idea what the version is, set it to a default
"0.0.0-SNAPSHOT"
}
}
}
}
}
}
res
})
def gitShortHash(): String = {
val hash = exec("git rev-parse --short HEAD")
assert(hash.length == 1)
hash(0)
}
// update the manifest version to include the git hash
lazy val manifestVersion = packageOptions in (Compile, packageBin) <++= version map { v => {
val version = "%s-%s".format(v, gitShortHash)
Seq(
Package.ManifestAttributes(java.util.jar.Attributes.Name.IMPLEMENTATION_VERSION -> version),
Package.ManifestAttributes(java.util.jar.Attributes.Name.SPECIFICATION_VERSION -> version)
)
}}
s ++= manifestVersion
// by default, sbt-pgp tries to read ~/.gnupg for keys. We need to force it
// to be .sbt, which makes it easier to pass the keys around without having
// to import into gnupg keyrings
lazy val pgpSettings = Seq(
pgpPublicRing := file(System.getProperty("user.home")) / ".sbt" / "gpg" / "daffodil" / "pubring.asc",
pgpSecretRing := file(System.getProperty("user.home")) / ".sbt" / "gpg" / "daffodil" / "secring.asc"
)
s ++= pgpSettings
}