summaryrefslogtreecommitdiff
path: root/build.sbt
diff options
context:
space:
mode:
Diffstat (limited to 'build.sbt')
-rw-r--r--build.sbt158
1 files changed, 89 insertions, 69 deletions
diff --git a/build.sbt b/build.sbt
index 4a8f349a59..786311e77c 100644
--- a/build.sbt
+++ b/build.sbt
@@ -17,40 +17,19 @@
* This nicely leads me to explaining goal and non-goals of this build definition. Goals are:
*
* - to be easy to tweak it in case a bug or small inconsistency is found
- * - to mimic Ant's behavior as closely as possible
* - to be super explicit about any departure from standard sbt settings
- * - to achieve functional parity with Ant build as quickly as possible
* - to be readable and not necessarily succinct
* - to provide the nicest development experience for people hacking on Scala
+ * - originally, to mimic Ant's behavior as closely as possible, so the
+ * sbt and Ant builds could be maintained in parallel. the Ant build
+ * has now been removed, so we are now free to depart from that history.
*
* Non-goals are:
*
- * - to have the shortest sbt build definition possible; we'll beat Ant definition
- * easily and that will thrill us already
+ * - to have the shortest sbt build definition possible
* - to remove irregularities from our build process right away
+ * (but let's keep making gradual progress on this)
* - to modularize the Scala compiler or library further
- *
- * It boils down to simple rules:
- *
- * - project layout is set in stone for now
- * - if you need to work on convincing sbt to follow non-standard layout then
- * explain everything you did in comments
- * - constantly check where Ant build produces class files, artifacts, what kind of other
- * files generates and port all of that to here
- *
- * Note on bootstrapping:
- *
- * Let's start with reminder what bootstrapping means in our context. It's an answer
- * to this question: which version of Scala are using to compile Scala? The fact that
- * the question sounds circular suggests trickiness. Indeed, bootstrapping Scala
- * compiler is a tricky process.
- *
- * Ant build used to have involved system of bootstrapping Scala. It would consist of
- * three layers: starr, locker and quick. The sbt build for Scala ditches layering
- * and strives to be as standard sbt project as possible. This means that we are simply
- * building Scala with latest stable release of Scala.
- * See this discussion for more details behind this decision:
- * https://groups.google.com/d/topic/scala-internals/gp5JsM1E0Fo/discussion
*/
import VersionUtil._
@@ -69,8 +48,8 @@ val asmDep = "org.scala-lang.modules" % "scala-asm" % versionPr
val jlineDep = "jline" % "jline" % versionProps("jline.version")
val antDep = "org.apache.ant" % "ant" % "1.9.4"
-/** Publish to ./dists/maven-sbt, similar to the ANT build which publishes to ./dists/maven. This
- * can be used to compare the output of the sbt and ANT builds during the transition period. Any
+/** Publish to ./dists/maven-sbt, similar to the Ant build which publishes to ./dists/maven. This
+ * can be used to compare the output of the sbt and Ant builds during the transition period. Any
* real publishing should be done with sbt's standard `publish` task. */
lazy val publishDists = taskKey[Unit]("Publish to ./dists/maven-sbt.")
@@ -109,7 +88,7 @@ lazy val publishSettings : Seq[Setting[_]] = Seq(
globalVersionSettings
baseVersion in Global := "2.12.0"
baseVersionSuffix in Global := "SNAPSHOT"
-mimaReferenceVersion in Global := None
+mimaReferenceVersion in Global := Some("2.12.0-RC1")
lazy val commonSettings = clearSourceAndResourceDirectories ++ publishSettings ++ Seq[Setting[_]](
organization := "org.scala-lang",
@@ -130,14 +109,16 @@ lazy val commonSettings = clearSourceAndResourceDirectories ++ publishSettings +
}
},
scalaVersion := (scalaVersion in bootstrap).value,
+ // As of sbt 0.13.12 (sbt/sbt#2634) sbt endeavours to align both scalaOrganization and scalaVersion
+ // in the Scala artefacts, for example scala-library and scala-compiler.
+ // This doesn't work in the scala/scala build because the version of scala-library and the scalaVersion of
+ // scala-library are correct to be different. So disable overriding.
+ ivyScala ~= (_ map (_ copy (overrideScalaVersion = false))),
// we always assume that Java classes are standalone and do not have any dependency
// on Scala classes
compileOrder := CompileOrder.JavaThenScala,
javacOptions in Compile ++= Seq("-g", "-source", "1.8", "-target", "1.8", "-Xlint:unchecked"),
- // we don't want any unmanaged jars; as a reminder: unmanaged jar is a jar stored
- // directly on the file system and it's not resolved through Ivy
- // Ant's build stored unmanaged jars in `lib/` directory
- unmanagedJars in Compile := Seq.empty,
+ unmanagedJars in Compile := Seq.empty, // no JARs in version control!
sourceDirectory in Compile := baseDirectory.value,
unmanagedSourceDirectories in Compile := List(baseDirectory.value),
unmanagedResourceDirectories in Compile += (baseDirectory in ThisBuild).value / "src" / thisProject.value.id,
@@ -184,7 +165,7 @@ lazy val commonSettings = clearSourceAndResourceDirectories ++ publishSettings +
<developers>
<developer>
<id>lamp</id>
- <name>EPFL LAMP</name>
+ <name>LAMP/EPFL</name>
</developer>
<developer>
<id>Lightbend</id>
@@ -214,10 +195,10 @@ lazy val commonSettings = clearSourceAndResourceDirectories ++ publishSettings +
// directly to stdout
outputStrategy in run := Some(StdoutOutput),
Quiet.silenceScalaBinaryVersionWarning
-)
+) ++ removePomDependencies
/** Extra post-processing for the published POM files. These are needed to create POMs that
- * are equivalent to the ones from the ANT build. In the long term this should be removed and
+ * are equivalent to the ones from the Ant build. In the long term this should be removed and
* POMs, scaladocs, OSGi manifests, etc. should all use the same metadata. */
def fixPom(extra: (String, scala.xml.Node)*): Setting[_] = {
/** Find elements in an XML document by a simple XPath and replace them */
@@ -243,10 +224,16 @@ def fixPom(extra: (String, scala.xml.Node)*): Setting[_] = {
) ++ extra) }
}
+val pomDependencyExclusions =
+ settingKey[Seq[(String, String)]]("List of (groupId, artifactId) pairs to exclude from the POM and ivy.xml")
+
+pomDependencyExclusions in Global := Nil
+
/** Remove unwanted dependencies from the POM and ivy.xml. */
-def removePomDependencies(deps: (String, String)*): Seq[Setting[_]] = Seq(
+lazy val removePomDependencies: Seq[Setting[_]] = Seq(
pomPostProcess := { n =>
val n2 = pomPostProcess.value.apply(n)
+ val deps = pomDependencyExclusions.value
import scala.xml._
import scala.xml.transform._
new RuleTransformer(new RewriteRule {
@@ -264,6 +251,7 @@ def removePomDependencies(deps: (String, String)*): Seq[Setting[_]] = Seq(
import scala.xml._
import scala.xml.transform._
val f = deliverLocal.value
+ val deps = pomDependencyExclusions.value
val e = new RuleTransformer(new RewriteRule {
override def transform(node: Node) = node match {
case e: Elem if e.label == "dependency" && {
@@ -314,8 +302,6 @@ def filterDocSources(ff: FileFilter): Seq[Setting[_]] = Seq(
// always required because otherwise the compiler cannot even initialize Definitions without
// binaries of the library on the classpath. Specifically, we get this error:
// (library/compile:doc) scala.reflect.internal.FatalError: package class scala does not have a member Int
- // Ant build does the same thing always: it puts binaries for documented classes on the classpath
- // sbt never does this by default (which seems like a good default)
dependencyClasspath in (Compile, doc) += (classDirectory in Compile).value,
doc in Compile <<= doc in Compile dependsOn (compile in Compile)
)
@@ -358,7 +344,9 @@ lazy val library = configureAsSubproject(project)
"/project/name" -> <name>Scala Library</name>,
"/project/description" -> <description>Standard library for the Scala Programming Language</description>,
"/project/packaging" -> <packaging>jar</packaging>
- )
+ ),
+ // Remove the dependency on "forkjoin" from the POM because it is included in the JAR:
+ pomDependencyExclusions += ((organization.value, "forkjoin"))
)
.settings(filterDocSources("*.scala" -- (regexFileFilter(".*/runtime/.*\\$\\.scala") ||
regexFileFilter(".*/runtime/ScalaRunTime\\.scala") ||
@@ -390,6 +378,7 @@ lazy val reflect = configureAsSubproject(project)
lazy val compiler = configureAsSubproject(project)
.settings(generatePropertiesFileSettings: _*)
+ .settings(generateBuildCharacterFileSettings: _*)
.settings(Osgi.settings: _*)
.settings(
name := "scala-compiler",
@@ -397,9 +386,11 @@ lazy val compiler = configureAsSubproject(project)
libraryDependencies ++= Seq(antDep, asmDep),
// These are only needed for the POM:
libraryDependencies ++= Seq(scalaXmlDep, jlineDep % "optional"),
+ buildCharacterPropertiesFile := (resourceManaged in Compile).value / "scala-buildcharacter.properties",
+ resourceGenerators in Compile += generateBuildCharacterPropertiesFile.map(file => Seq(file)).taskValue,
// this a way to make sure that classes from interactive and scaladoc projects
- // end up in compiler jar (that's what Ant build does)
- // we need to use LocalProject references (with strings) to deal with mutual recursion
+ // end up in compiler jar. note that we need to use LocalProject references
+ // (with strings) to deal with mutual recursion
products in Compile in packageBin :=
(products in Compile in packageBin).value ++
Seq((dependencyClasspath in Compile).value.find(_.get(moduleID.key) == Some(asmDep)).get.data) ++
@@ -430,21 +421,18 @@ lazy val compiler = configureAsSubproject(project)
"*"),
"Class-Path" -> "scala-reflect.jar scala-library.jar"
),
- // Generate the ScriptEngineFactory service definition. The ant build does this when building
+ // Generate the ScriptEngineFactory service definition. The Ant build does this when building
// the JAR but sbt has no support for it and it is easier to do as a resource generator:
- generateServiceProviderResources("javax.script.ScriptEngineFactory" -> "scala.tools.nsc.interpreter.IMain$Factory"),
+ generateServiceProviderResources("javax.script.ScriptEngineFactory" -> "scala.tools.nsc.interpreter.Scripted$Factory"),
managedResourceDirectories in Compile := Seq((resourceManaged in Compile).value),
fixPom(
"/project/name" -> <name>Scala Compiler</name>,
"/project/description" -> <description>Compiler for the Scala Programming Language</description>,
"/project/packaging" -> <packaging>jar</packaging>
),
- apiURL := None
+ apiURL := None,
+ pomDependencyExclusions ++= List(("org.apache.ant", "ant"), ("org.scala-lang.modules", "scala-asm"))
)
- .settings(removePomDependencies(
- ("org.apache.ant", "ant"),
- ("org.scala-lang.modules", "scala-asm")
- ): _*)
.dependsOn(library, reflect)
lazy val interactive = configureAsSubproject(project)
@@ -482,14 +470,14 @@ lazy val replJlineEmbedded = Project("repl-jline-embedded", file(".") / "target"
// There is nothing to compile for this project. Instead we use the compile task to create
// shaded versions of repl-jline and jline.jar. dist/mkBin puts all of quick/repl,
// quick/repl-jline and quick/repl-jline-shaded on the classpath for quick/bin scripts.
- // This is different from the ant build where all parts are combined into quick/repl, but
+ // This is different from the Ant build where all parts are combined into quick/repl, but
// it is cleaner because it avoids circular dependencies.
compile in Compile <<= (compile in Compile).dependsOn(Def.task {
import java.util.jar._
import collection.JavaConverters._
val inputs: Iterator[JarJar.Entry] = {
val repljlineClasses = (products in Compile in replJline).value.flatMap(base => Path.allSubpaths(base).map(x => (base, x._1)))
- val jlineJAR = (dependencyClasspath in Compile).value.find(_.get(moduleID.key) == Some(jlineDep)).get.data
+ val jlineJAR = findJar((dependencyClasspath in Compile).value, jlineDep).get.data
val jarFile = new JarFile(jlineJAR)
val jarEntries = jarFile.entries.asScala.filterNot(_.isDirectory).map(entry => JarJar.JarEntryInput(jarFile, entry))
def compiledClasses = repljlineClasses.iterator.map { case (base, file) => JarJar.FileInput(base, file) }
@@ -536,8 +524,10 @@ lazy val scalap = configureAsSubproject(project)
)
.dependsOn(compiler)
-lazy val partestExtras = configureAsSubproject(Project("partest-extras", file(".") / "src" / "partest-extras"))
+lazy val partestExtras = Project("partest-extras", file(".") / "src" / "partest-extras")
.dependsOn(replJlineEmbedded)
+ .settings(commonSettings: _*)
+ .settings(generatePropertiesFileSettings: _*)
.settings(clearSourceAndResourceDirectories: _*)
.settings(disableDocs: _*)
.settings(disablePublishing: _*)
@@ -623,8 +613,6 @@ lazy val partestJavaAgent = Project("partest-javaagent", file(".") / "src" / "pa
// Setting name to "scala-partest-javaagent" so that the jar file gets that name, which the Runner relies on
name := "scala-partest-javaagent",
description := "Scala Compiler Testing Tool (compiler-specific java agent)",
- // writing jar file to $buildDirectory/pack/lib because that's where it's expected to be found
- setJarLocation,
// add required manifest entry - previously included from file
packageOptions in (Compile, packageBin) +=
Package.ManifestAttributes( "Premain-Class" -> "scala.tools.partest.javaagent.ProfilingAgent" ),
@@ -658,7 +646,7 @@ lazy val test = project
javaOptions in IntegrationTest += "-Xmx2G",
testFrameworks += new TestFramework("scala.tools.partest.sbt.Framework"),
testFrameworks -= new TestFramework("org.scalacheck.ScalaCheckFramework"),
- testOptions in IntegrationTest += Tests.Argument("-Dpartest.java_opts=-Xmx1024M -Xms64M -XX:MaxPermSize=128M"),
+ testOptions in IntegrationTest += Tests.Argument("-Dpartest.java_opts=-Xmx1024M -Xms64M"),
testOptions in IntegrationTest += Tests.Argument("-Dpartest.scalac_opts=" + (scalacOptions in Compile).value.mkString(" ")),
testOptions in IntegrationTest += Tests.Setup { () =>
val cp = (dependencyClasspath in Test).value
@@ -675,17 +663,26 @@ lazy val test = project
def isModule = true
def annotationName = "partest"
}, true, Array()
- )
+ ),
+ executeTests in IntegrationTest := {
+ val result = (executeTests in IntegrationTest).value
+ if (result.overall != TestResult.Error && result.events.isEmpty) {
+ // workaround for https://github.com/sbt/sbt/issues/2722
+ val result = (executeTests in Test).value
+ (streams.value.log.error("No test events found"))
+ result.copy(overall = TestResult.Error)
+ }
+ else result
+ }
)
lazy val manual = configureAsSubproject(project)
.settings(disableDocs: _*)
.settings(disablePublishing: _*)
.settings(
- libraryDependencies ++= Seq(scalaXmlDep, antDep),
+ libraryDependencies ++= Seq(scalaXmlDep, antDep, "org.scala-lang" % "scala-library" % scalaVersion.value),
classDirectory in Compile := (target in Compile).value / "classes"
)
- .dependsOn(library)
lazy val libraryAll = Project("library-all", file(".") / "target" / "library-all-src-dummy")
.settings(commonSettings: _*)
@@ -732,7 +729,7 @@ lazy val scalaDist = Project("scala-dist", file(".") / "target" / "scala-dist-di
(manOut ** "*.1" pair rebase(manOut, fixedManOut)).foreach { case (in, out) =>
// Generated manpages should always use LF only. There doesn't seem to be a good reason
// for generating them with the platform EOL first and then converting them but that's
- // what the ant build does.
+ // what the Ant build does.
IO.write(out, IO.readBytes(in).filterNot(_ == '\r'))
}
(htmlOut ** "*.html").get ++ (fixedManOut ** "*.1").get
@@ -757,6 +754,18 @@ lazy val root: Project = (project in file("."))
publish := {},
publishLocal := {},
commands ++= ScriptCommands.all,
+ extractBuildCharacterPropertiesFile := {
+ val jar = (scalaInstance in bootstrap).value.compilerJar
+ val bc = buildCharacterPropertiesFile.value
+ val packagedName = "scala-buildcharacter.properties"
+ IO.withTemporaryDirectory { tmp =>
+ val extracted = IO.unzip(jar, tmp, new SimpleFilter(_ == packagedName)).headOption.getOrElse {
+ throw new RuntimeException(s"No file $packagedName found in bootstrap compiler $jar")
+ }
+ IO.copyFile(extracted, bc)
+ bc
+ }
+ },
// Generate (Product|TupleN|Function|AbstractFunction)*.scala files and scaladoc stubs for all AnyVal sources.
// They should really go into a managedSources dir instead of overwriting sources checked into git but scaladoc
// source links (could be fixed by shipping these sources with the scaladoc bundles) and scala-js source maps
@@ -812,25 +821,30 @@ lazy val root: Project = (project in file("."))
k.scope.config.toOption.map(_.name + ":"),
k.scope.task.toOption.map(_.label + "::")
).flatten.mkString + k.key
- def logIncomplete(i: Incomplete, prefix: String): Unit = {
+ val loggedThis, loggedAny = new scala.collection.mutable.HashSet[String]
+ def findRootCauses(i: Incomplete, currentTask: String): Vector[(String, Option[Throwable])] = {
val sk = i.node match {
case Some(t: Task[_]) =>
t.info.attributes.entries.collect { case e if e.key == Keys.taskDefinitionKey => e.value.asInstanceOf[Def.ScopedKey[_]] }
.headOption.map(showScopedKey)
case _ => None
}
- val childCount = (if(i.directCause.isDefined) 1 else 0) + i.causes.length
- val skip = childCount <= 1 && sk.isEmpty
- if(!skip) log.error(s"$prefix- ${sk.getOrElse("?")}")
- i.directCause match {
- case Some(e) => log.error(s"$prefix - $e")
- case None => i.causes.foreach(i => logIncomplete(i, prefix + (if(skip) "" else " ")))
+ val task = sk.getOrElse(currentTask)
+ val dup = sk.map(s => !loggedAny.add(s)).getOrElse(false)
+ if(sk.map(s => !loggedThis.add(s)).getOrElse(false)) Vector.empty
+ else i.directCause match {
+ case Some(e) => Vector((task, if(dup) None else Some(e)))
+ case None => i.causes.toVector.flatMap(ch => findRootCauses(ch, task))
}
}
log.error(s"${failed.size} of ${results.length} test tasks failed:")
failed.foreach { case (i, d) =>
log.error(s"- $d")
- logIncomplete(i, " ")
+ loggedThis.clear
+ findRootCauses(i, "<unkown task>").foreach {
+ case (task, Some(ex)) => log.error(s" - $task failed: $ex")
+ case (task, None) => log.error(s" - ($task failed)")
+ }
}
throw new RuntimeException
}
@@ -846,7 +860,7 @@ lazy val root: Project = (project in file("."))
)
// The following subprojects' binaries are required for building "pack":
-lazy val distDependencies = Seq(replJline, replJlineEmbedded, compiler, library, partestExtras, partestJavaAgent, reflect, scalap, scaladoc)
+lazy val distDependencies = Seq(replJline, replJlineEmbedded, compiler, library, reflect, scalap, scaladoc)
lazy val dist = (project in file("dist"))
.settings(commonSettings)
@@ -871,7 +885,7 @@ lazy val dist = (project in file("dist"))
val extraJars = (externalDependencyClasspath in Compile).value.map(a => (a.get(moduleID.key), a.data)).collect {
case (Some(m), f) if extraModules contains uniqueModule(m) => f
}
- val jlineJAR = (dependencyClasspath in Compile).value.find(_.get(moduleID.key) == Some(jlineDep)).get.data
+ val jlineJAR = findJar((dependencyClasspath in Compile).value, jlineDep).get.data
val mappings = extraJars.map(f => (f, targetDir / f.getName)) :+ ((jlineJAR, targetDir / "jline.jar"))
IO.copy(mappings, overwrite = true)
targetDir
@@ -1143,3 +1157,9 @@ intellijToSample := {
} else
s.log.info("Aborting.")
}
+
+/** Find a specific module's JAR in a classpath, comparing only organization and name */
+def findJar(files: Seq[Attributed[File]], dep: ModuleID): Option[Attributed[File]] = {
+ def extract(m: ModuleID) = (m.organization, m.name)
+ files.find(_.get(moduleID.key).map(extract _) == Some(extract(dep)))
+}