diff options
Diffstat (limited to 'build.sbt')
-rw-r--r-- | build.sbt | 158 |
1 files changed, 89 insertions, 69 deletions
@@ -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))) +} |