diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 000000000..0d5990db2 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,18 @@ +maxColumn = 100 +project.git = true +project.excludeFilters = [ /sbt-test/, /input_sources/, /contraband-scala/ ] + +# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style. +# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala +docstrings = JavaDoc + +# This also seems more idiomatic to include whitespace in import x.{ yyy } +spaces.inImportCurlyBraces = true + +# This works around sequence wildcard (`_*`) turning into `_ *` +spaces.beforeSeqWildcard = true + +# Vertical alignment only => for pattern matching +align.tokens.add = [ + { code = "=>", owner = "Case" } +] diff --git a/.travis.yml b/.travis.yml index c39d887fa..59cb0e286 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ matrix: env: matrix: - - SBT_CMD=";test:compile;scalariformCheck" + - SBT_CMD=";test:compile;scalafmtCheck" - SBT_CMD="mimaReportBinaryIssues" - SBT_CMD="safeUnitTests" - SBT_CMD="otherUnitTests" diff --git a/build.sbt b/build.sbt index 79bd12670..33a6c8956 100644 --- a/build.sbt +++ b/build.sbt @@ -3,71 +3,77 @@ import Dependencies._ import Sxr.sxr import com.typesafe.tools.mima.core._, ProblemFilters._ -import com.typesafe.tools.mima.plugin.MimaKeys.{ binaryIssueFilters, previousArtifact} +import com.typesafe.tools.mima.plugin.MimaKeys.{ binaryIssueFilters, previousArtifact } import com.typesafe.tools.mima.plugin.MimaPlugin.mimaDefaultSettings // ThisBuild settings take lower precedence, // but can be shared across the multi projects. -def buildLevelSettings: Seq[Setting[_]] = inThisBuild(Seq( - organization := "org.scala-sbt", - version := "1.0.0-SNAPSHOT", - description := "sbt is an interactive build tool", - bintrayOrganization := Some("sbt"), - bintrayRepository := { - if (publishStatus.value == "releases") "maven-releases" - else "maven-snapshots" - }, - bintrayPackage := "sbt", - bintrayReleaseOnPublish := false, - licenses := List("BSD New" -> url("https://github.com/sbt/sbt/blob/0.13/LICENSE")), - developers := List( - Developer("harrah", "Mark Harrah", "@harrah", url("https://github.com/harrah")), - Developer("eed3si9n", "Eugene Yokota", "@eed3si9n", url("https://github.com/eed3si9n")), - Developer("jsuereth", "Josh Suereth", "@jsuereth", url("https://github.com/jsuereth")), - Developer("dwijnand", "Dale Wijnand", "@dwijnand", url("https://github.com/dwijnand")), - Developer("gkossakowski", "Grzegorz Kossakowski", "@gkossakowski", url("https://github.com/gkossakowski")), - Developer("Duhemm", "Martin Duhem", "@Duhemm", url("https://github.com/Duhemm")) - ), - homepage := Some(url("https://github.com/sbt/sbt")), - scmInfo := Some(ScmInfo(url("https://github.com/sbt/sbt"), "git@github.com:sbt/sbt.git")), - resolvers += Resolver.mavenLocal -)) +def buildLevelSettings: Seq[Setting[_]] = + inThisBuild( + Seq( + organization := "org.scala-sbt", + version := "1.0.0-SNAPSHOT", + description := "sbt is an interactive build tool", + bintrayOrganization := Some("sbt"), + bintrayRepository := { + if (publishStatus.value == "releases") "maven-releases" + else "maven-snapshots" + }, + bintrayPackage := "sbt", + bintrayReleaseOnPublish := false, + licenses := List("BSD New" -> url("https://github.com/sbt/sbt/blob/0.13/LICENSE")), + developers := List( + Developer("harrah", "Mark Harrah", "@harrah", url("https://github.com/harrah")), + Developer("eed3si9n", "Eugene Yokota", "@eed3si9n", url("https://github.com/eed3si9n")), + Developer("jsuereth", "Josh Suereth", "@jsuereth", url("https://github.com/jsuereth")), + Developer("dwijnand", "Dale Wijnand", "@dwijnand", url("https://github.com/dwijnand")), + Developer("gkossakowski", + "Grzegorz Kossakowski", + "@gkossakowski", + url("https://github.com/gkossakowski")), + Developer("Duhemm", "Martin Duhem", "@Duhemm", url("https://github.com/Duhemm")) + ), + homepage := Some(url("https://github.com/sbt/sbt")), + scmInfo := Some(ScmInfo(url("https://github.com/sbt/sbt"), "git@github.com:sbt/sbt.git")), + resolvers += Resolver.mavenLocal + )) -def commonSettings: Seq[Setting[_]] = Seq[SettingsDefinition]( - scalaVersion := baseScalaVersion, - componentID := None, - resolvers += Resolver.typesafeIvyRepo("releases"), - resolvers += Resolver.sonatypeRepo("snapshots"), - resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/", - concurrentRestrictions in Global += Util.testExclusiveRestriction, - testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"), - javacOptions in compile ++= Seq("-target", "6", "-source", "6", "-Xlint", "-Xlint:-serial"), - incOptions := incOptions.value.withNameHashing(true), - crossScalaVersions := Seq(baseScalaVersion), - bintrayPackage := (bintrayPackage in ThisBuild).value, - bintrayRepository := (bintrayRepository in ThisBuild).value, - mimaDefaultSettings, - publishArtifact in Test := false, - mimaPreviousArtifacts := Set.empty, // Set(organization.value % moduleName.value % "1.0.0"), - mimaBinaryIssueFilters ++= Seq( - ) -) flatMap (_.settings) +def commonSettings: Seq[Setting[_]] = + Seq[SettingsDefinition]( + scalaVersion := baseScalaVersion, + componentID := None, + resolvers += Resolver.typesafeIvyRepo("releases"), + resolvers += Resolver.sonatypeRepo("snapshots"), + resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/", + concurrentRestrictions in Global += Util.testExclusiveRestriction, + testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"), + javacOptions in compile ++= Seq("-target", "6", "-source", "6", "-Xlint", "-Xlint:-serial"), + incOptions := incOptions.value.withNameHashing(true), + crossScalaVersions := Seq(baseScalaVersion), + bintrayPackage := (bintrayPackage in ThisBuild).value, + bintrayRepository := (bintrayRepository in ThisBuild).value, + mimaDefaultSettings, + publishArtifact in Test := false, + mimaPreviousArtifacts := Set.empty, // Set(organization.value % moduleName.value % "1.0.0"), + mimaBinaryIssueFilters ++= Seq( + ) + ) flatMap (_.settings) def minimalSettings: Seq[Setting[_]] = commonSettings ++ customCommands ++ - publishPomSettings ++ Release.javaVersionCheckSettings + publishPomSettings ++ Release.javaVersionCheckSettings def baseSettings: Seq[Setting[_]] = - minimalSettings ++ Seq(projectComponent) ++ baseScalacOptions ++ Licensed.settings ++ Formatting.settings + minimalSettings ++ Seq(projectComponent) ++ baseScalacOptions ++ Licensed.settings def testedBaseSettings: Seq[Setting[_]] = baseSettings ++ testDependencies -lazy val sbtRoot: Project = (project in file(".")). - enablePlugins(ScriptedPlugin). - configs(Sxr.sxrConf). - aggregate(nonRoots: _*). - settings( +lazy val sbtRoot: Project = (project in file(".")) + .enablePlugins(ScriptedPlugin) + .configs(Sxr.sxrConf) + .aggregate(nonRoots: _*) + .settings( buildLevelSettings, minimalSettings, rootSettings, @@ -77,112 +83,121 @@ lazy val sbtRoot: Project = (project in file(".")). // This is used to configure an sbt-launcher for this version of sbt. lazy val bundledLauncherProj = - (project in file("launch")). - settings( - minimalSettings, - inConfig(Compile)(Transform.configSettings), - Release.launcherSettings(sbtLaunchJar) - ). - enablePlugins(SbtLauncherPlugin). - settings( - name := "sbt-launch", - moduleName := "sbt-launch", - description := "sbt application launcher", - autoScalaLibrary := false, - crossPaths := false, - publish := Release.deployLauncher.value, - publishLauncher := Release.deployLauncher.value, - packageBin in Compile := sbtLaunchJar.value - ) + (project in file("launch")) + .settings( + minimalSettings, + inConfig(Compile)(Transform.configSettings), + Release.launcherSettings(sbtLaunchJar) + ) + .enablePlugins(SbtLauncherPlugin) + .settings( + name := "sbt-launch", + moduleName := "sbt-launch", + description := "sbt application launcher", + autoScalaLibrary := false, + crossPaths := false, + publish := Release.deployLauncher.value, + publishLauncher := Release.deployLauncher.value, + packageBin in Compile := sbtLaunchJar.value + ) /* ** subproject declarations ** */ /* **** Intermediate-level Modules **** */ // Runner for uniform test interface -lazy val testingProj = (project in file("testing")). - enablePlugins(ContrabandPlugin, JsonCodecPlugin). - dependsOn(testAgentProj). - settings( +lazy val testingProj = (project in file("testing")) + .enablePlugins(ContrabandPlugin, JsonCodecPlugin) + .dependsOn(testAgentProj) + .settings( baseSettings, name := "Testing", - libraryDependencies ++= Seq(testInterface,launcherInterface, sjsonNewScalaJson), + libraryDependencies ++= Seq(testInterface, launcherInterface, sjsonNewScalaJson), managedSourceDirectories in Compile += baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats - ). - configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging) + ) + .configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging) // Testing agent for running tests in a separate process. -lazy val testAgentProj = (project in file("testing") / "agent"). - settings( - minimalSettings, - crossScalaVersions := Seq(baseScalaVersion), - crossPaths := false, - autoScalaLibrary := false, - name := "Test Agent", - libraryDependencies += testInterface - ) +lazy val testAgentProj = (project in file("testing") / "agent").settings( + minimalSettings, + crossScalaVersions := Seq(baseScalaVersion), + crossPaths := false, + autoScalaLibrary := false, + name := "Test Agent", + libraryDependencies += testInterface +) // Basic task engine -lazy val taskProj = (project in file("tasks")). - settings( +lazy val taskProj = (project in file("tasks")) + .settings( testedBaseSettings, name := "Tasks" - ). - configure(addSbtUtilControl, addSbtUtilCollection) + ) + .configure(addSbtUtilControl, addSbtUtilCollection) // Standard task system. This provides map, flatMap, join, and more on top of the basic task model. -lazy val stdTaskProj = (project in file("tasks-standard")). - dependsOn (taskProj % "compile;test->test"). - settings( +lazy val stdTaskProj = (project in file("tasks-standard")) + .dependsOn(taskProj % "compile;test->test") + .settings( testedBaseSettings, name := "Task System", testExclusive - ). - configure(addSbtUtilCollection, addSbtUtilLogging, addSbtUtilCache, addSbtIO) + ) + .configure(addSbtUtilCollection, addSbtUtilLogging, addSbtUtilCache, addSbtIO) // Embedded Scala code runner -lazy val runProj = (project in file("run")). - settings( +lazy val runProj = (project in file("run")) + .settings( testedBaseSettings, name := "Run" - ). - configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath) + ) + .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath) -lazy val scriptedSbtProj = (project in scriptedPath / "sbt"). - dependsOn(commandProj). - settings( +lazy val scriptedSbtProj = (project in scriptedPath / "sbt") + .dependsOn(commandProj) + .settings( baseSettings, name := "Scripted sbt", libraryDependencies ++= Seq(launcherInterface % "provided") - ). - configure(addSbtIO, addSbtUtilLogging, addSbtCompilerInterface, addSbtUtilScripted) + ) + .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerInterface, addSbtUtilScripted) -lazy val scriptedPluginProj = (project in scriptedPath / "plugin"). - dependsOn(sbtProj). - settings( +lazy val scriptedPluginProj = (project in scriptedPath / "plugin") + .dependsOn(sbtProj) + .settings( baseSettings, name := "Scripted Plugin" - ). - configure(addSbtCompilerClasspath) + ) + .configure(addSbtCompilerClasspath) // Implementation and support code for defining actions. -lazy val actionsProj = (project in file("main-actions")). - dependsOn(runProj, stdTaskProj, taskProj, testingProj). - settings( +lazy val actionsProj = (project in file("main-actions")) + .dependsOn(runProj, stdTaskProj, taskProj, testingProj) + .settings( testedBaseSettings, name := "Actions", libraryDependencies += sjsonNewScalaJson - ). - configure(addSbtCompilerClasspath, addSbtUtilCompletion, addSbtCompilerApiInfo, - addSbtZinc, addSbtCompilerIvyIntegration, addSbtCompilerInterface, - addSbtIO, addSbtUtilLogging, addSbtUtilRelation, addSbtLm, addSbtUtilTracking) + ) + .configure( + addSbtCompilerClasspath, + addSbtUtilCompletion, + addSbtCompilerApiInfo, + addSbtZinc, + addSbtCompilerIvyIntegration, + addSbtCompilerInterface, + addSbtIO, + addSbtUtilLogging, + addSbtUtilRelation, + addSbtLm, + addSbtUtilTracking + ) -lazy val protocolProj = (project in file("protocol")). - enablePlugins(ContrabandPlugin, JsonCodecPlugin). - settings( +lazy val protocolProj = (project in file("protocol")) + .enablePlugins(ContrabandPlugin, JsonCodecPlugin) + .settings( testedBaseSettings, name := "Protocol", libraryDependencies ++= Seq(sjsonNewScalaJson), @@ -190,14 +205,14 @@ lazy val protocolProj = (project in file("protocol")). baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats - ). - configure(addSbtUtilLogging) + ) + .configure(addSbtUtilLogging) // General command support and core commands not specific to a build system -lazy val commandProj = (project in file("main-command")). - enablePlugins(ContrabandPlugin, JsonCodecPlugin). - dependsOn(protocolProj). - settings( +lazy val commandProj = (project in file("main-command")) + .enablePlugins(ContrabandPlugin, JsonCodecPlugin) + .dependsOn(protocolProj) + .settings( testedBaseSettings, name := "Command", libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson, templateResolverApi), @@ -205,44 +220,61 @@ lazy val commandProj = (project in file("main-command")). baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats - ). - configure(addSbtCompilerInterface, addSbtIO, addSbtUtilLogging, addSbtUtilCompletion, addSbtCompilerClasspath, addSbtLm) + ) + .configure(addSbtCompilerInterface, + addSbtIO, + addSbtUtilLogging, + addSbtUtilCompletion, + addSbtCompilerClasspath, + addSbtLm) // Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions -lazy val mainSettingsProj = (project in file("main-settings")). - dependsOn(commandProj, stdTaskProj). - settings( +lazy val mainSettingsProj = (project in file("main-settings")) + .dependsOn(commandProj, stdTaskProj) + .settings( testedBaseSettings, name := "Main Settings" - ). - configure(addSbtUtilCache, addSbtUtilApplyMacro, addSbtCompilerInterface, addSbtUtilRelation, - addSbtUtilLogging, addSbtIO, addSbtUtilCompletion, addSbtCompilerClasspath, addSbtLm) + ) + .configure( + addSbtUtilCache, + addSbtUtilApplyMacro, + addSbtCompilerInterface, + addSbtUtilRelation, + addSbtUtilLogging, + addSbtIO, + addSbtUtilCompletion, + addSbtCompilerClasspath, + addSbtLm + ) // The main integration project for sbt. It brings all of the projects together, configures them, and provides for overriding conventions. -lazy val mainProj = (project in file("main")). - dependsOn(actionsProj, mainSettingsProj, runProj, commandProj). - disablePlugins(SbtScalariform). - settings( +lazy val mainProj = (project in file("main")) + .dependsOn(actionsProj, mainSettingsProj, runProj, commandProj) + .settings( testedBaseSettings, name := "Main", libraryDependencies ++= scalaXml.value ++ Seq(launcherInterface) - ). - configure(addSbtCompilerInterface, - addSbtIO, addSbtUtilLogging, addSbtUtilLogic, addSbtLm, addSbtZincCompile) + ) + .configure(addSbtCompilerInterface, + addSbtIO, + addSbtUtilLogging, + addSbtUtilLogic, + addSbtLm, + addSbtZincCompile) // Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object // technically, we need a dependency on all of mainProj's dependencies, but we don't do that since this is strictly an integration project // with the sole purpose of providing certain identifiers without qualification (with a package object) -lazy val sbtProj = (project in file("sbt")). - dependsOn(mainProj, scriptedSbtProj % "test->test"). - settings( +lazy val sbtProj = (project in file("sbt")) + .dependsOn(mainProj, scriptedSbtProj % "test->test") + .settings( baseSettings, name := "sbt", normalizedName := "sbt", crossScalaVersions := Seq(baseScalaVersion), crossPaths := false - ). - configure(addSbtCompilerBridge) + ) + .configure(addSbtCompilerBridge) def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed @@ -252,52 +284,88 @@ def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { // that alternate repo to the running scripted test (in Scripted.scriptedpreScripted). // (altLocalPublish in interfaceProj).value // (altLocalPublish in compileInterfaceProj).value - Scripted.doScripted((sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value, + Scripted.doScripted( + (sbtLaunchJar in bundledLauncherProj).value, + (fullClasspath in scriptedSbtProj in Test).value, (scalaInstance in scriptedSbtProj).value, - scriptedSource.value, scriptedBufferLog.value, result, scriptedPrescripted.value, scriptedLaunchOpts.value) + scriptedSource.value, + scriptedBufferLog.value, + result, + scriptedPrescripted.value, + scriptedLaunchOpts.value + ) } def scriptedUnpublishedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { val result = scriptedSource(dir => (s: State) => Scripted.scriptedParser(dir)).parsed - Scripted.doScripted((sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value, + Scripted.doScripted( + (sbtLaunchJar in bundledLauncherProj).value, + (fullClasspath in scriptedSbtProj in Test).value, (scalaInstance in scriptedSbtProj).value, - scriptedSource.value, scriptedBufferLog.value, result, scriptedPrescripted.value, scriptedLaunchOpts.value) + scriptedSource.value, + scriptedBufferLog.value, + result, + scriptedPrescripted.value, + scriptedLaunchOpts.value + ) } lazy val publishLauncher = TaskKey[Unit]("publish-launcher") lazy val myProvided = config("provided") intransitive -def allProjects = Seq( - testingProj, testAgentProj, taskProj, stdTaskProj, runProj, - scriptedSbtProj, scriptedPluginProj, protocolProj, - actionsProj, commandProj, mainSettingsProj, mainProj, sbtProj, bundledLauncherProj) +def allProjects = + Seq( + testingProj, + testAgentProj, + taskProj, + stdTaskProj, + runProj, + scriptedSbtProj, + scriptedPluginProj, + protocolProj, + actionsProj, + commandProj, + mainSettingsProj, + mainProj, + sbtProj, + bundledLauncherProj + ) -def projectsWithMyProvided = allProjects.map(p => p.copy(configurations = (p.configurations.filter(_ != Provided)) :+ myProvided)) +def projectsWithMyProvided = + allProjects.map(p => + p.copy(configurations = (p.configurations.filter(_ != Provided)) :+ myProvided)) lazy val nonRoots = projectsWithMyProvided.map(p => LocalProject(p.id)) -def rootSettings = fullDocSettings ++ - Util.publishPomSettings ++ otherRootSettings ++ Formatting.sbtFilesSettings ++ - Transform.conscriptSettings(bundledLauncherProj) -def otherRootSettings = Seq( - scripted := scriptedTask.evaluated, - scriptedUnpublished := scriptedUnpublishedTask.evaluated, - scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test", - // scriptedPrescripted := { addSbtAlternateResolver _ }, - scriptedLaunchOpts := List("-XX:MaxPermSize=256M", "-Xmx1G"), - publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value }, - publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value }, - aggregate in bintrayRelease := false -) ++ inConfig(Scripted.RepoOverrideTest)(Seq( - scriptedPrescripted := { _ => () }, - scriptedLaunchOpts := { - List("-XX:MaxPermSize=256M", "-Xmx1G", "-Dsbt.override.build.repos=true", - s"""-Dsbt.repository.config=${ scriptedSource.value / "repo.config" }""") - }, - scripted := scriptedTask.evaluated, - scriptedUnpublished := scriptedUnpublishedTask.evaluated, - scriptedSource := (sourceDirectory in sbtProj).value / "repo-override-test" -)) +def rootSettings = + fullDocSettings ++ + Util.publishPomSettings ++ otherRootSettings ++ + Transform.conscriptSettings(bundledLauncherProj) +def otherRootSettings = + Seq( + scripted := scriptedTask.evaluated, + scriptedUnpublished := scriptedUnpublishedTask.evaluated, + scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test", + // scriptedPrescripted := { addSbtAlternateResolver _ }, + scriptedLaunchOpts := List("-XX:MaxPermSize=256M", "-Xmx1G"), + publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value }, + publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value }, + aggregate in bintrayRelease := false + ) ++ inConfig(Scripted.RepoOverrideTest)( + Seq( + scriptedPrescripted := { _ => + () + }, + scriptedLaunchOpts := { + List("-XX:MaxPermSize=256M", + "-Xmx1G", + "-Dsbt.override.build.repos=true", + s"""-Dsbt.repository.config=${scriptedSource.value / "repo.config"}""") + }, + scripted := scriptedTask.evaluated, + scriptedUnpublished := scriptedUnpublishedTask.evaluated, + scriptedSource := (sourceDirectory in sbtProj).value / "repo-override-test" + )) // def addSbtAlternateResolver(scriptedRoot: File) = { // val resolver = scriptedRoot / "project" / "AddResolverPlugin.scala" @@ -337,17 +405,20 @@ def fullDocSettings = Util.baseScalacOptions ++ Docs.settings ++ Sxr.settings ++ lazy val safeUnitTests = taskKey[Unit]("Known working tests (for both 2.10 and 2.11)") lazy val safeProjects: ScopeFilter = ScopeFilter( - inProjects(mainSettingsProj, mainProj, - actionsProj, runProj, stdTaskProj), + inProjects(mainSettingsProj, mainProj, actionsProj, runProj, stdTaskProj), inConfigurations(Test) ) lazy val otherUnitTests = taskKey[Unit]("Unit test other projects") lazy val otherProjects: ScopeFilter = ScopeFilter( - inProjects( - testingProj, testAgentProj, taskProj, - scriptedSbtProj, scriptedPluginProj, - commandProj, mainSettingsProj, mainProj, - sbtProj), + inProjects(testingProj, + testAgentProj, + taskProj, + scriptedSbtProj, + scriptedPluginProj, + commandProj, + mainSettingsProj, + mainProj, + sbtProj), inConfigurations(Test) ) @@ -362,12 +433,19 @@ def customCommands: Seq[Setting[_]] = Seq( otherUnitTests := { test.all(otherProjects).value }, + commands += Command.command("scalafmtCheck") { state => + sys.process.Process("git diff --name-only --exit-code").! match { + case 0 => // ok + case x => sys.error("git diff detected! Did you compile before committing?") + } + state + }, commands += Command.command("release-sbt-local") { state => "clean" :: - "so compile" :: - "so publishLocal" :: - "reload" :: - state + "so compile" :: + "so publishLocal" :: + "reload" :: + state }, /** There are several complications with sbt's build. * First is the fact that interface project is a Java-only project diff --git a/main-actions/src/main/scala/sbt/Compiler.scala b/main-actions/src/main/scala/sbt/Compiler.scala index 7a319b5bb..2e1ca9dc7 100644 --- a/main-actions/src/main/scala/sbt/Compiler.scala +++ b/main-actions/src/main/scala/sbt/Compiler.scala @@ -4,7 +4,13 @@ package sbt import sbt.internal.inc.javac.JavaTools -import sbt.internal.inc.{ AnalyzingCompiler, ComponentCompiler, ScalaInstance, ZincComponentManager, IncrementalCompilerImpl } +import sbt.internal.inc.{ + AnalyzingCompiler, + ComponentCompiler, + ScalaInstance, + ZincComponentManager, + IncrementalCompilerImpl +} import xsbti.{ Logger => _, _ } import xsbti.compile.{ ClasspathOptions, Compilers, CompileResult, Inputs } import java.io.File @@ -24,7 +30,9 @@ object Compiler { } private[this] def scalaCompilerBridgeSource(suffix: String): ModuleID = - ModuleID(xsbti.ArtifactInfo.SbtOrganization, s"compiler-bridge_$suffix", ComponentCompiler.incrementalVersion) + ModuleID(xsbti.ArtifactInfo.SbtOrganization, + s"compiler-bridge_$suffix", + ComponentCompiler.incrementalVersion) .withConfigurations(Some("component")) .sources() @@ -33,7 +41,8 @@ object Compiler { private[sbt] def scalaCompilerBridgeSource2_12: ModuleID = scalaCompilerBridgeSource("2.12") def compilers( - cpOptions: ClasspathOptions, ivyConfiguration: IvyConfiguration + cpOptions: ClasspathOptions, + ivyConfiguration: IvyConfiguration )(implicit app: AppConfiguration, log: Logger): Compilers = { val scalaProvider = app.provider.scalaProvider val instance = ScalaInstance(scalaProvider.version, scalaProvider.launcher) @@ -43,8 +52,11 @@ object Compiler { // TODO: Get java compiler def compilers( - instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File], - ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID + instance: ScalaInstance, + cpOptions: ClasspathOptions, + javaHome: Option[File], + ivyConfiguration: IvyConfiguration, + sourcesModule: ModuleID )(implicit app: AppConfiguration, log: Logger): Compilers = { val scalac = scalaCompiler(instance, cpOptions, javaHome, ivyConfiguration, sourcesModule) val javac = JavaTools.directOrFork(instance, cpOptions, javaHome) @@ -52,12 +64,19 @@ object Compiler { } def scalaCompiler( - instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File], - ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID + instance: ScalaInstance, + cpOptions: ClasspathOptions, + javaHome: Option[File], + ivyConfiguration: IvyConfiguration, + sourcesModule: ModuleID )(implicit app: AppConfiguration, log: Logger): AnalyzingCompiler = { val launcher = app.provider.scalaProvider.launcher - val componentManager = new ZincComponentManager(launcher.globalLock, app.provider.components, Option(launcher.ivyHome), log) - val provider = ComponentCompiler.interfaceProvider(componentManager, ivyConfiguration, sourcesModule) + val componentManager = new ZincComponentManager(launcher.globalLock, + app.provider.components, + Option(launcher.ivyHome), + log) + val provider = + ComponentCompiler.interfaceProvider(componentManager, ivyConfiguration, sourcesModule) new AnalyzingCompiler(instance, provider, cpOptions, _ => (), None) } @@ -66,5 +85,11 @@ object Compiler { def compile(in: Inputs, log: Logger): CompileResult = compiler.compile(in, log) private[sbt] def foldMappers[A](mappers: Seq[A => Option[A]]) = - mappers.foldRight({ p: A => p }) { (mapper, mappers) => { p: A => mapper(p).getOrElse(mappers(p)) } } + mappers.foldRight({ p: A => + p + }) { (mapper, mappers) => + { p: A => + mapper(p).getOrElse(mappers(p)) + } + } } diff --git a/main-actions/src/main/scala/sbt/Console.scala b/main-actions/src/main/scala/sbt/Console.scala index a06d72069..c8ddf6544 100644 --- a/main-actions/src/main/scala/sbt/Console.scala +++ b/main-actions/src/main/scala/sbt/Console.scala @@ -11,23 +11,36 @@ import xsbti.compile.{ Inputs, Compilers } import scala.util.Try final class Console(compiler: AnalyzingCompiler) { + /** Starts an interactive scala interpreter session with the given classpath.*/ def apply(classpath: Seq[File], log: Logger): Try[Unit] = apply(classpath, Nil, "", "", log) - def apply(classpath: Seq[File], options: Seq[String], initialCommands: String, cleanupCommands: String, log: Logger): Try[Unit] = + def apply(classpath: Seq[File], + options: Seq[String], + initialCommands: String, + cleanupCommands: String, + log: Logger): Try[Unit] = apply(classpath, options, initialCommands, cleanupCommands)(None, Nil)(log) - def apply(classpath: Seq[File], options: Seq[String], loader: ClassLoader, initialCommands: String, cleanupCommands: String)(bindings: (String, Any)*)(implicit log: Logger): Try[Unit] = + def apply(classpath: Seq[File], + options: Seq[String], + loader: ClassLoader, + initialCommands: String, + cleanupCommands: String)(bindings: (String, Any)*)(implicit log: Logger): Try[Unit] = apply(classpath, options, initialCommands, cleanupCommands)(Some(loader), bindings) - def apply(classpath: Seq[File], options: Seq[String], initialCommands: String, cleanupCommands: String)(loader: Option[ClassLoader], bindings: Seq[(String, Any)])(implicit log: Logger): Try[Unit] = - { - def console0() = compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings) - // TODO: Fix JLine - //JLine.withJLine(Run.executeTrapExit(console0, log)) - Run.executeTrapExit(console0, log) - } + def apply(classpath: Seq[File], + options: Seq[String], + initialCommands: String, + cleanupCommands: String)(loader: Option[ClassLoader], bindings: Seq[(String, Any)])( + implicit log: Logger): Try[Unit] = { + def console0() = + compiler.console(classpath, options, initialCommands, cleanupCommands, log)(loader, bindings) + // TODO: Fix JLine + //JLine.withJLine(Run.executeTrapExit(console0, log)) + Run.executeTrapExit(console0, log) + } } object Console { def apply(conf: Inputs): Console = diff --git a/main-actions/src/main/scala/sbt/Doc.scala b/main-actions/src/main/scala/sbt/Doc.scala index 4324c017d..2b0b662f9 100644 --- a/main-actions/src/main/scala/sbt/Doc.scala +++ b/main-actions/src/main/scala/sbt/Doc.scala @@ -19,26 +19,67 @@ import sbt.internal.util.ManagedLogger object Doc { import RawCompileLike._ - def scaladoc(label: String, cacheStoreFactory: CacheStoreFactory, compiler: AnalyzingCompiler): Gen = + def scaladoc(label: String, + cacheStoreFactory: CacheStoreFactory, + compiler: AnalyzingCompiler): Gen = scaladoc(label, cacheStoreFactory, compiler, Seq()) - def scaladoc(label: String, cacheStoreFactory: CacheStoreFactory, compiler: AnalyzingCompiler, fileInputOptions: Seq[String]): Gen = - cached(cacheStoreFactory, fileInputOptions, prepare(label + " Scala API documentation", compiler.doc)) - def javadoc(label: String, cacheStoreFactory: CacheStoreFactory, doc: JavaTools, log: Logger, reporter: Reporter): Gen = + def scaladoc(label: String, + cacheStoreFactory: CacheStoreFactory, + compiler: AnalyzingCompiler, + fileInputOptions: Seq[String]): Gen = + cached(cacheStoreFactory, + fileInputOptions, + prepare(label + " Scala API documentation", compiler.doc)) + def javadoc(label: String, + cacheStoreFactory: CacheStoreFactory, + doc: JavaTools, + log: Logger, + reporter: Reporter): Gen = javadoc(label, cacheStoreFactory, doc, log, reporter, Seq()) - def javadoc(label: String, cacheStoreFactory: CacheStoreFactory, doc: JavaTools, log: Logger, reporter: Reporter, fileInputOptions: Seq[String]): Gen = - cached(cacheStoreFactory, fileInputOptions, prepare(label + " Java API documentation", filterSources( - javaSourcesOnly, - (sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maxErrors: Int, log: Logger) => { - // doc.doc - ??? - } - ))) + def javadoc(label: String, + cacheStoreFactory: CacheStoreFactory, + doc: JavaTools, + log: Logger, + reporter: Reporter, + fileInputOptions: Seq[String]): Gen = + cached( + cacheStoreFactory, + fileInputOptions, + prepare( + label + " Java API documentation", + filterSources( + javaSourcesOnly, + (sources: Seq[File], + classpath: Seq[File], + outputDirectory: File, + options: Seq[String], + maxErrors: Int, + log: Logger) => { + // doc.doc + ??? + } + ) + ) + ) val javaSourcesOnly: File => Boolean = _.getName.endsWith(".java") private[sbt] final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler) extends Doc { - def apply(label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: ManagedLogger): Unit = { - generate("Scala", label, compiler.doc, sources, classpath, outputDirectory, options, maximumErrors, log) + def apply(label: String, + sources: Seq[File], + classpath: Seq[File], + outputDirectory: File, + options: Seq[String], + log: ManagedLogger): Unit = { + generate("Scala", + label, + compiler.doc, + sources, + classpath, + outputDirectory, + options, + maximumErrors, + log) } } } @@ -46,12 +87,21 @@ object Doc { sealed trait Doc { type Gen = (Seq[File], Seq[File], File, Seq[String], Int, ManagedLogger) => Unit - private[sbt] final def generate(variant: String, label: String, docf: Gen, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maxErrors: Int, log: ManagedLogger): Unit = { + private[sbt] final def generate(variant: String, + label: String, + docf: Gen, + sources: Seq[File], + classpath: Seq[File], + outputDirectory: File, + options: Seq[String], + maxErrors: Int, + log: ManagedLogger): Unit = { val logSnip = variant + " API documentation" if (sources.isEmpty) log.info("No sources available, skipping " + logSnip + "...") else { - log.info("Generating " + logSnip + " for " + label + " sources to " + outputDirectory.absolutePath + "...") + log.info( + "Generating " + logSnip + " for " + label + " sources to " + outputDirectory.absolutePath + "...") IO.delete(outputDirectory) IO.createDirectory(outputDirectory) docf(sources, classpath, outputDirectory, options, maxErrors, log) diff --git a/main-actions/src/main/scala/sbt/DotGraph.scala b/main-actions/src/main/scala/sbt/DotGraph.scala index 5d927daa4..96fb50743 100644 --- a/main-actions/src/main/scala/sbt/DotGraph.scala +++ b/main-actions/src/main/scala/sbt/DotGraph.scala @@ -18,24 +18,37 @@ object DotGraph { apply(relations, outputDirectory, toString, toString) } def packages(relations: Relations, outputDirectory: File, sourceRoots: Iterable[File]): Unit = { - val packageOnly = (path: String) => - { - val last = path.lastIndexOf(File.separatorChar.toInt) - val packagePath = (if (last > 0) path.substring(0, last) else path).trim - if (packagePath.isEmpty) "" else packagePath.replace(File.separatorChar, '.') - } + val packageOnly = (path: String) => { + val last = path.lastIndexOf(File.separatorChar.toInt) + val packagePath = (if (last > 0) path.substring(0, last) else path).trim + if (packagePath.isEmpty) "" else packagePath.replace(File.separatorChar, '.') + } val toString = packageOnly compose fToString(sourceRoots) apply(relations, outputDirectory, toString, toString) } - def apply(relations: Relations, outputDir: File, sourceToString: File => String, externalToString: File => String): Unit = { + def apply(relations: Relations, + outputDir: File, + sourceToString: File => String, + externalToString: File => String): Unit = { def file(name: String) = new File(outputDir, name) IO.createDirectory(outputDir) - generateGraph(file("int-class-deps"), "dependencies", relations.internalClassDep, identity[String], identity[String]) - generateGraph(file("binary-dependencies"), "externalDependencies", relations.libraryDep, externalToString, sourceToString) + generateGraph(file("int-class-deps"), + "dependencies", + relations.internalClassDep, + identity[String], + identity[String]) + generateGraph(file("binary-dependencies"), + "externalDependencies", + relations.libraryDep, + externalToString, + sourceToString) } - def generateGraph[K, V](file: File, graphName: String, relation: Relation[K, V], - keyToString: K => String, valueToString: V => String): Unit = { + def generateGraph[K, V](file: File, + graphName: String, + relation: Relation[K, V], + keyToString: K => String, + valueToString: V => String): Unit = { import scala.collection.mutable.{ HashMap, HashSet } val mappedGraph = new HashMap[String, HashSet[String]] for ((key, values) <- relation.forwardMap; keyString = keyToString(key); value <- values) @@ -58,10 +71,9 @@ object DotGraph { def sourceToString(roots: Iterable[File], source: File) = relativized(roots, source).trim.stripSuffix(".scala").stripSuffix(".java") - private def relativized(roots: Iterable[File], path: File): String = - { - val relativized = roots.flatMap(root => IO.relativize(root, path)) - val shortest = (Int.MaxValue /: relativized)(_ min _.length) - relativized.find(_.length == shortest).getOrElse(path.getName) - } + private def relativized(roots: Iterable[File], path: File): String = { + val relativized = roots.flatMap(root => IO.relativize(root, path)) + val shortest = (Int.MaxValue /: relativized)(_ min _.length) + relativized.find(_.length == shortest).getOrElse(path.getName) + } } diff --git a/main-actions/src/main/scala/sbt/ForkTests.scala b/main-actions/src/main/scala/sbt/ForkTests.scala index bcf395193..58a3b0f20 100755 --- a/main-actions/src/main/scala/sbt/ForkTests.scala +++ b/main-actions/src/main/scala/sbt/ForkTests.scala @@ -15,7 +15,13 @@ import sbt.ConcurrentRestrictions.Tag import sbt.protocol.testing._ private[sbt] object ForkTests { - def apply(runners: Map[TestFramework, Runner], tests: List[TestDefinition], config: Execution, classpath: Seq[File], fork: ForkOptions, log: Logger, tag: Tag): Task[TestOutput] = { + def apply(runners: Map[TestFramework, Runner], + tests: List[TestDefinition], + config: Execution, + classpath: Seq[File], + fork: ForkOptions, + log: Logger, + tag: Tag): Task[TestOutput] = { val opts = processOptions(config, tests, log) import std.TaskExtra._ @@ -32,7 +38,12 @@ private[sbt] object ForkTests { } } - private[this] def mainTestTask(runners: Map[TestFramework, Runner], opts: ProcessedOptions, classpath: Seq[File], fork: ForkOptions, log: Logger, parallel: Boolean): Task[TestOutput] = + private[this] def mainTestTask(runners: Map[TestFramework, Runner], + opts: ProcessedOptions, + classpath: Seq[File], + fork: ForkOptions, + log: Logger, + parallel: Boolean): Task[TestOutput] = std.TaskExtra.task { val server = new ServerSocket(0) val testListeners = opts.testListeners flatMap { @@ -42,7 +53,8 @@ private[sbt] object ForkTests { object Acceptor extends Runnable { val resultsAcc = mutable.Map.empty[String, SuiteResult] - lazy val result = TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty) + lazy val result = + TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty) def run(): Unit = { val socket = @@ -50,7 +62,8 @@ private[sbt] object ForkTests { server.accept() } catch { case e: java.net.SocketException => - log.error("Could not accept connection from test agent: " + e.getClass + ": " + e.getMessage) + log.error( + "Could not accept connection from test agent: " + e.getClass + ": " + e.getMessage) log.trace(e) server.close() return @@ -64,7 +77,12 @@ private[sbt] object ForkTests { val config = new ForkConfiguration(log.ansiCodesSupported, parallel) os.writeObject(config) - val taskdefs = opts.tests.map(t => new TaskDef(t.name, forkFingerprint(t.fingerprint), t.explicitlySpecified, t.selectors)) + val taskdefs = opts.tests.map( + t => + new TaskDef(t.name, + forkFingerprint(t.fingerprint), + t.explicitlySpecified, + t.selectors)) os.writeObject(taskdefs.toArray) os.writeInt(runners.size) @@ -79,7 +97,8 @@ private[sbt] object ForkTests { } catch { case NonFatal(e) => def throwableToString(t: Throwable) = { - import java.io._; val sw = new StringWriter; t.printStackTrace(new PrintWriter(sw)); sw.toString + import java.io._; val sw = new StringWriter; t.printStackTrace(new PrintWriter(sw)); + sw.toString } resultsAcc("Forked test harness failed: " + throwableToString(e)) = SuiteResult.Error } finally { @@ -93,12 +112,20 @@ private[sbt] object ForkTests { val acceptorThread = new Thread(Acceptor) acceptorThread.start() - val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], IO.classLocationFile[Framework]) - val options = Seq("-classpath", fullCp mkString File.pathSeparator, classOf[ForkMain].getCanonicalName, server.getLocalPort.toString) + val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], + IO.classLocationFile[Framework]) + val options = Seq("-classpath", + fullCp mkString File.pathSeparator, + classOf[ForkMain].getCanonicalName, + server.getLocalPort.toString) val ec = Fork.java(fork, options) val result = if (ec != 0) - TestOutput(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), Iterable.empty) + TestOutput(TestResult.Error, + Map( + "Running java with options " + options + .mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), + Iterable.empty) else { // Need to wait acceptor thread to finish its business acceptorThread.join() @@ -119,9 +146,14 @@ private[sbt] object ForkTests { case _ => sys.error("Unknown fingerprint type: " + f.getClass) } } -private final class React(is: ObjectInputStream, os: ObjectOutputStream, log: Logger, listeners: Seq[TestReportListener], results: mutable.Map[String, SuiteResult]) { +private final class React(is: ObjectInputStream, + os: ObjectOutputStream, + log: Logger, + listeners: Seq[TestReportListener], + results: mutable.Map[String, SuiteResult]) { import ForkTags._ - @annotation.tailrec def react(): Unit = is.readObject match { + @annotation.tailrec + def react(): Unit = is.readObject match { case `Done` => os.writeObject(Done); os.flush() case Array(`Error`, s: String) => diff --git a/main-actions/src/main/scala/sbt/Package.scala b/main-actions/src/main/scala/sbt/Package.scala index 552f29c74..b27534082 100644 --- a/main-actions/src/main/scala/sbt/Package.scala +++ b/main-actions/src/main/scala/sbt/Package.scala @@ -27,11 +27,10 @@ object Package { } final case class MainClass(mainClassName: String) extends PackageOption final case class ManifestAttributes(attributes: (Attributes.Name, String)*) extends PackageOption - def ManifestAttributes(attributes: (String, String)*): ManifestAttributes = - { - val converted = for ((name, value) <- attributes) yield (new Attributes.Name(name), value) - new ManifestAttributes(converted: _*) - } + def ManifestAttributes(attributes: (String, String)*): ManifestAttributes = { + val converted = for ((name, value) <- attributes) yield (new Attributes.Name(name), value) + new ManifestAttributes(converted: _*) + } def mergeAttributes(a1: Attributes, a2: Attributes) = a1.asScala ++= a2.asScala // merges `mergeManifest` into `manifest` (mutating `manifest` in the process) @@ -46,7 +45,9 @@ object Package { } } - final class Configuration(val sources: Seq[(File, String)], val jar: File, val options: Seq[PackageOption]) + final class Configuration(val sources: Seq[(File, String)], + val jar: File, + val options: Seq[PackageOption]) def apply(conf: Configuration, cacheStoreFactory: CacheStoreFactory, log: Logger): Unit = { val manifest = new Manifest val main = manifest.getMainAttributes @@ -60,15 +61,17 @@ object Package { } setVersion(main) - val cachedMakeJar = inputChanged(cacheStoreFactory make "inputs") { (inChanged, inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil) => - import exists.format - val sources :+: _ :+: manifest :+: HNil = inputs - inputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) => - if (inChanged || outChanged) - makeJar(sources.toSeq, jar.file, manifest, log) - else - log.debug("Jar uptodate: " + jar.file) - } + val cachedMakeJar = inputChanged(cacheStoreFactory make "inputs") { + (inChanged, + inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil) => + import exists.format + val sources :+: _ :+: manifest :+: HNil = inputs + inputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) => + if (inChanged || outChanged) + makeJar(sources.toSeq, jar.file, manifest, log) + else + log.debug("Jar uptodate: " + jar.file) + } } val map = conf.sources.toMap @@ -80,20 +83,27 @@ object Package { if (main.getValue(version) eq null) main.put(version, "1.0") } - def addSpecManifestAttributes(name: String, version: String, orgName: String): PackageOption = - { - import Attributes.Name._ - val attribKeys = Seq(SPECIFICATION_TITLE, SPECIFICATION_VERSION, SPECIFICATION_VENDOR) - val attribVals = Seq(name, version, orgName) - ManifestAttributes(attribKeys zip attribVals: _*) - } - def addImplManifestAttributes(name: String, version: String, homepage: Option[java.net.URL], org: String, orgName: String): PackageOption = - { - import Attributes.Name._ - val attribKeys = Seq(IMPLEMENTATION_TITLE, IMPLEMENTATION_VERSION, IMPLEMENTATION_VENDOR, IMPLEMENTATION_VENDOR_ID) - val attribVals = Seq(name, version, orgName, org) - ManifestAttributes((attribKeys zip attribVals) ++ { homepage map (h => (IMPLEMENTATION_URL, h.toString)) }: _*) - } + def addSpecManifestAttributes(name: String, version: String, orgName: String): PackageOption = { + import Attributes.Name._ + val attribKeys = Seq(SPECIFICATION_TITLE, SPECIFICATION_VERSION, SPECIFICATION_VENDOR) + val attribVals = Seq(name, version, orgName) + ManifestAttributes(attribKeys zip attribVals: _*) + } + def addImplManifestAttributes(name: String, + version: String, + homepage: Option[java.net.URL], + org: String, + orgName: String): PackageOption = { + import Attributes.Name._ + val attribKeys = Seq(IMPLEMENTATION_TITLE, + IMPLEMENTATION_VERSION, + IMPLEMENTATION_VENDOR, + IMPLEMENTATION_VENDOR_ID) + val attribVals = Seq(name, version, orgName, org) + ManifestAttributes((attribKeys zip attribVals) ++ { + homepage map (h => (IMPLEMENTATION_URL, h.toString)) + }: _*) + } def makeJar(sources: Seq[(File, String)], jar: File, manifest: Manifest, log: Logger): Unit = { val path = jar.getAbsolutePath log.info("Packaging " + path + " ...") diff --git a/main-actions/src/main/scala/sbt/RawCompileLike.scala b/main-actions/src/main/scala/sbt/RawCompileLike.scala index a1669bbe2..bd2a20618 100644 --- a/main-actions/src/main/scala/sbt/RawCompileLike.scala +++ b/main-actions/src/main/scala/sbt/RawCompileLike.scala @@ -23,43 +23,49 @@ import sbt.internal.util.ManagedLogger object RawCompileLike { type Gen = (Seq[File], Seq[File], File, Seq[String], Int, ManagedLogger) => Unit - private def optionFiles(options: Seq[String], fileInputOpts: Seq[String]): List[File] = - { - @annotation.tailrec - def loop(opt: List[String], result: List[File]): List[File] = { - opt.dropWhile(!fileInputOpts.contains(_)) match { - case List(_, fileOpt, tail @ _*) => - { - val file = new File(fileOpt) - if (file.isFile) loop(tail.toList, file :: result) - else loop(tail.toList, result) - } - case Nil | List(_) => result + private def optionFiles(options: Seq[String], fileInputOpts: Seq[String]): List[File] = { + @annotation.tailrec + def loop(opt: List[String], result: List[File]): List[File] = { + opt.dropWhile(!fileInputOpts.contains(_)) match { + case List(_, fileOpt, tail @ _*) => { + val file = new File(fileOpt) + if (file.isFile) loop(tail.toList, file :: result) + else loop(tail.toList, result) } + case Nil | List(_) => result } - loop(options.toList, Nil) } + loop(options.toList, Nil) + } - def cached(cacheStoreFactory: CacheStoreFactory, doCompile: Gen): Gen = cached(cacheStoreFactory, Seq(), doCompile) - def cached(cacheStoreFactory: CacheStoreFactory, fileInputOpts: Seq[String], doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => - { - type Inputs = FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+: Seq[String] :+: Int :+: HNil - val inputs: Inputs = hash(sources.toSet ++ optionFiles(options, fileInputOpts)) :+: lastModified(classpath.toSet) :+: classpath :+: outputDirectory :+: options :+: maxErrors :+: HNil + def cached(cacheStoreFactory: CacheStoreFactory, doCompile: Gen): Gen = + cached(cacheStoreFactory, Seq(), doCompile) + def cached(cacheStoreFactory: CacheStoreFactory, + fileInputOpts: Seq[String], + doCompile: Gen): Gen = + (sources, classpath, outputDirectory, options, maxErrors, log) => { + type Inputs = + FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+: Seq[ + String] :+: Int :+: HNil + val inputs + : Inputs = hash(sources.toSet ++ optionFiles(options, fileInputOpts)) :+: lastModified( + classpath.toSet) :+: classpath :+: outputDirectory :+: options :+: maxErrors :+: HNil implicit val stringEquiv: Equiv[String] = defaultEquiv implicit val fileEquiv: Equiv[File] = defaultEquiv implicit val intEquiv: Equiv[Int] = defaultEquiv val cachedComp = inputChanged(cacheStoreFactory make "inputs") { (inChanged, in: Inputs) => - inputChanged(cacheStoreFactory make "output") { (outChanged, outputs: FilesInfo[PlainFileInfo]) => - if (inChanged || outChanged) - doCompile(sources, classpath, outputDirectory, options, maxErrors, log) - else - log.debug("Uptodate: " + outputDirectory.getAbsolutePath) + inputChanged(cacheStoreFactory make "output") { + (outChanged, outputs: FilesInfo[PlainFileInfo]) => + if (inChanged || outChanged) + doCompile(sources, classpath, outputDirectory, options, maxErrors, log) + else + log.debug("Uptodate: " + outputDirectory.getAbsolutePath) } } cachedComp(inputs)(exists(outputDirectory.allPaths.get.toSet)) } - def prepare(description: String, doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => - { + def prepare(description: String, doCompile: Gen): Gen = + (sources, classpath, outputDirectory, options, maxErrors, log) => { if (sources.isEmpty) log.info("No sources available, skipping " + description + "...") else { @@ -70,15 +76,19 @@ object RawCompileLike { log.info(description.capitalize + " successful.") } } - def filterSources(f: File => Boolean, doCompile: Gen): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => - doCompile(sources filter f, classpath, outputDirectory, options, maxErrors, log) + def filterSources(f: File => Boolean, doCompile: Gen): Gen = + (sources, classpath, outputDirectory, options, maxErrors, log) => + doCompile(sources filter f, classpath, outputDirectory, options, maxErrors, log) - def rawCompile(instance: ScalaInstance, cpOptions: ClasspathOptions): Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => - { + def rawCompile(instance: ScalaInstance, cpOptions: ClasspathOptions): Gen = + (sources, classpath, outputDirectory, options, maxErrors, log) => { val compiler = new RawCompiler(instance, cpOptions, log) compiler(sources, classpath, outputDirectory, options) } - def compile(label: String, cacheStoreFactory: CacheStoreFactory, instance: ScalaInstance, cpOptions: ClasspathOptions): Gen = + def compile(label: String, + cacheStoreFactory: CacheStoreFactory, + instance: ScalaInstance, + cpOptions: ClasspathOptions): Gen = cached(cacheStoreFactory, prepare(label + " sources", rawCompile(instance, cpOptions))) val nop: Gen = (sources, classpath, outputDirectory, options, maxErrors, log) => () diff --git a/main-actions/src/main/scala/sbt/Sync.scala b/main-actions/src/main/scala/sbt/Sync.scala index f19973719..ce05cfe0d 100644 --- a/main-actions/src/main/scala/sbt/Sync.scala +++ b/main-actions/src/main/scala/sbt/Sync.scala @@ -26,42 +26,44 @@ import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError } * It is safe to use for its intended purpose: copying resources to a class output directory. */ object Sync { - def apply(store: CacheStore, inStyle: FileInfo.Style = FileInfo.lastModified, outStyle: FileInfo.Style = FileInfo.exists): Traversable[(File, File)] => Relation[File, File] = - mappings => - { - val relation = Relation.empty ++ mappings - noDuplicateTargets(relation) - val currentInfo = relation._1s.map(s => (s, inStyle(s))).toMap + def apply(store: CacheStore, + inStyle: FileInfo.Style = FileInfo.lastModified, + outStyle: FileInfo.Style = FileInfo.exists) + : Traversable[(File, File)] => Relation[File, File] = + mappings => { + val relation = Relation.empty ++ mappings + noDuplicateTargets(relation) + val currentInfo = relation._1s.map(s => (s, inStyle(s))).toMap - val (previousRelation, previousInfo) = readInfo(store)(inStyle.format) - val removeTargets = previousRelation._2s -- relation._2s + val (previousRelation, previousInfo) = readInfo(store)(inStyle.format) + val removeTargets = previousRelation._2s -- relation._2s - def outofdate(source: File, target: File): Boolean = - !previousRelation.contains(source, target) || - (previousInfo get source) != (currentInfo get source) || - !target.exists || - target.isDirectory != source.isDirectory + def outofdate(source: File, target: File): Boolean = + !previousRelation.contains(source, target) || + (previousInfo get source) != (currentInfo get source) || + !target.exists || + target.isDirectory != source.isDirectory - val updates = relation filter outofdate + val updates = relation filter outofdate - val (cleanDirs, cleanFiles) = (updates._2s ++ removeTargets).partition(_.isDirectory) + val (cleanDirs, cleanFiles) = (updates._2s ++ removeTargets).partition(_.isDirectory) - IO.delete(cleanFiles) - IO.deleteIfEmpty(cleanDirs) - updates.all.foreach((copy _).tupled) + IO.delete(cleanFiles) + IO.deleteIfEmpty(cleanDirs) + updates.all.foreach((copy _).tupled) - writeInfo(store, relation, currentInfo)(inStyle.format) - relation - } + writeInfo(store, relation, currentInfo)(inStyle.format) + relation + } def copy(source: File, target: File): Unit = if (source.isFile) IO.copyFile(source, target, true) else if (!target.exists) // we don't want to update the last modified time of an existing directory - { - IO.createDirectory(target) - IO.copyLastModified(source, target) - } + { + IO.createDirectory(target) + IO.copyLastModified(source, target) + } def noDuplicateTargets(relation: Relation[File, File]): Unit = { val dups = relation.reverseMap.filter { @@ -75,7 +77,8 @@ object Sync { sys.error("Duplicate mappings:" + dups.mkString) } - implicit def relationFormat[A, B](implicit af: JsonFormat[Map[A, Set[B]]], bf: JsonFormat[Map[B, Set[A]]]): JsonFormat[Relation[A, B]] = + implicit def relationFormat[A, B](implicit af: JsonFormat[Map[A, Set[B]]], + bf: JsonFormat[Map[B, Set[A]]]): JsonFormat[Relation[A, B]] = new JsonFormat[Relation[A, B]] { def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Relation[A, B] = jsOpt match { @@ -98,12 +101,15 @@ object Sync { } - def writeInfo[F <: FileInfo](store: CacheStore, relation: Relation[File, File], info: Map[File, F])(implicit infoFormat: JsonFormat[F]): Unit = + def writeInfo[F <: FileInfo](store: CacheStore, + relation: Relation[File, File], + info: Map[File, F])(implicit infoFormat: JsonFormat[F]): Unit = store.write((relation, info)) type RelationInfo[F] = (Relation[File, File], Map[File, F]) - def readInfo[F <: FileInfo](store: CacheStore)(implicit infoFormat: JsonFormat[F]): RelationInfo[F] = + def readInfo[F <: FileInfo](store: CacheStore)( + implicit infoFormat: JsonFormat[F]): RelationInfo[F] = store.read(default = (Relation.empty[File, File], Map.empty[File, F])) } diff --git a/main-actions/src/main/scala/sbt/TestResultLogger.scala b/main-actions/src/main/scala/sbt/TestResultLogger.scala index 9715c42c5..0992937d5 100644 --- a/main-actions/src/main/scala/sbt/TestResultLogger.scala +++ b/main-actions/src/main/scala/sbt/TestResultLogger.scala @@ -24,11 +24,13 @@ trait TestResultLogger { def run(log: Logger, results: Output, taskName: String): Unit /** Only allow invocation if certain criteria is met, else use another `TestResultLogger` (defaulting to nothing) . */ - final def onlyIf(f: (Output, String) => Boolean, otherwise: TestResultLogger = TestResultLogger.Null) = + final def onlyIf(f: (Output, String) => Boolean, + otherwise: TestResultLogger = TestResultLogger.Null) = TestResultLogger.choose(f, this, otherwise) /** Allow invocation unless a certain predicate passes, in which case use another `TestResultLogger` (defaulting to nothing) . */ - final def unless(f: (Output, String) => Boolean, otherwise: TestResultLogger = TestResultLogger.Null) = + final def unless(f: (Output, String) => Boolean, + otherwise: TestResultLogger = TestResultLogger.Null) = TestResultLogger.choose(f, otherwise, this) } @@ -118,16 +120,32 @@ object TestResultLogger { results.summaries.size > 1 || results.summaries.headOption.forall(_.summaryText.isEmpty) val printStandard = TestResultLogger((log, results, _) => { - val (skippedCount, errorsCount, passedCount, failuresCount, ignoredCount, canceledCount, pendingCount) = + val (skippedCount, + errorsCount, + passedCount, + failuresCount, + ignoredCount, + canceledCount, + pendingCount) = results.events.foldLeft((0, 0, 0, 0, 0, 0, 0)) { - case ((skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc), (name, testEvent)) => - (skippedAcc + testEvent.skippedCount, errorAcc + testEvent.errorCount, passedAcc + testEvent.passedCount, failureAcc + testEvent.failureCount, - ignoredAcc + testEvent.ignoredCount, canceledAcc + testEvent.canceledCount, pendingAcc + testEvent.pendingCount) + case ((skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc), + (name, testEvent)) => + (skippedAcc + testEvent.skippedCount, + errorAcc + testEvent.errorCount, + passedAcc + testEvent.passedCount, + failureAcc + testEvent.failureCount, + ignoredAcc + testEvent.ignoredCount, + canceledAcc + testEvent.canceledCount, + pendingAcc + testEvent.pendingCount) } val totalCount = failuresCount + errorsCount + skippedCount + passedCount - val base = s"Total $totalCount, Failed $failuresCount, Errors $errorsCount, Passed $passedCount" + val base = + s"Total $totalCount, Failed $failuresCount, Errors $errorsCount, Passed $passedCount" - val otherCounts = Seq("Skipped" -> skippedCount, "Ignored" -> ignoredCount, "Canceled" -> canceledCount, "Pending" -> pendingCount) + val otherCounts = Seq("Skipped" -> skippedCount, + "Ignored" -> ignoredCount, + "Canceled" -> canceledCount, + "Pending" -> pendingCount) val extra = otherCounts.filter(_._2 > 0).map { case (label, count) => s", $label $count" } val postfix = base + extra.mkString @@ -155,7 +173,7 @@ object TestResultLogger { show("Error during tests:", Level.Error, select(TestResult.Error)) }) - val printNoTests = TestResultLogger((log, results, taskName) => - log.info("No tests to run for " + taskName)) + val printNoTests = TestResultLogger( + (log, results, taskName) => log.info("No tests to run for " + taskName)) } } diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index e178b95b7..bdd8f91b5 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -12,7 +12,16 @@ import xsbti.api.Definition import xsbti.compile.CompileAnalysis import ConcurrentRestrictions.Tag -import testing.{ AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner, TaskDef, SuiteSelector, Task => TestTask } +import testing.{ + AnnotatedFingerprint, + Fingerprint, + Framework, + SubclassFingerprint, + Runner, + TaskDef, + SuiteSelector, + Task => TestTask +} import scala.annotation.tailrec import sbt.internal.util.ManagedLogger @@ -21,6 +30,7 @@ import sbt.protocol.testing.TestResult sealed trait TestOption object Tests { + /** * The result of a test run. * @@ -28,7 +38,9 @@ object Tests { * @param events The result of each test group (suite) executed during this test run. * @param summaries Explicit summaries directly provided by test frameworks. This may be empty, in which case a default summary will be generated. */ - final case class Output(overall: TestResult, events: Map[String, SuiteResult], summaries: Iterable[Summary]) + final case class Output(overall: TestResult, + events: Map[String, SuiteResult], + summaries: Iterable[Summary]) /** * Summarizes a test run. @@ -88,7 +100,8 @@ object Tests { * If None, the arguments will apply to all test frameworks. * @param args The list of arguments to pass to the selected framework(s). */ - final case class Argument(framework: Option[TestFramework], args: List[String]) extends TestOption + final case class Argument(framework: Option[TestFramework], args: List[String]) + extends TestOption /** * Configures test execution. @@ -115,92 +128,134 @@ object Tests { final case class Group(name: String, tests: Seq[TestDefinition], runPolicy: TestRunPolicy) private[sbt] final class ProcessedOptions( - val tests: Seq[TestDefinition], - val setup: Seq[ClassLoader => Unit], - val cleanup: Seq[ClassLoader => Unit], - val testListeners: Seq[TestReportListener] + val tests: Seq[TestDefinition], + val setup: Seq[ClassLoader => Unit], + val cleanup: Seq[ClassLoader => Unit], + val testListeners: Seq[TestReportListener] ) - private[sbt] def processOptions(config: Execution, discovered: Seq[TestDefinition], log: Logger): ProcessedOptions = - { - import collection.mutable.{ HashSet, ListBuffer } - val testFilters = new ListBuffer[String => Boolean] - var orderedFilters = Seq[String => Boolean]() - val excludeTestsSet = new HashSet[String] - val setup, cleanup = new ListBuffer[ClassLoader => Unit] - val testListeners = new ListBuffer[TestReportListener] - val undefinedFrameworks = new ListBuffer[String] + private[sbt] def processOptions(config: Execution, + discovered: Seq[TestDefinition], + log: Logger): ProcessedOptions = { + import collection.mutable.{ HashSet, ListBuffer } + val testFilters = new ListBuffer[String => Boolean] + var orderedFilters = Seq[String => Boolean]() + val excludeTestsSet = new HashSet[String] + val setup, cleanup = new ListBuffer[ClassLoader => Unit] + val testListeners = new ListBuffer[TestReportListener] + val undefinedFrameworks = new ListBuffer[String] - for (option <- config.options) { - option match { - case Filter(include) => testFilters += include - case Filters(includes) => if (orderedFilters.nonEmpty) sys.error("Cannot define multiple ordered test filters.") else orderedFilters = includes - case Exclude(exclude) => excludeTestsSet ++= exclude - case Listeners(listeners) => testListeners ++= listeners - case Setup(setupFunction) => setup += setupFunction - case Cleanup(cleanupFunction) => cleanup += cleanupFunction - case a: Argument => // now handled by whatever constructs `runners` - } - } - - if (excludeTestsSet.nonEmpty) - log.debug(excludeTestsSet.mkString("Excluding tests: \n\t", "\n\t", "")) - if (undefinedFrameworks.nonEmpty) - log.warn("Arguments defined for test frameworks that are not present:\n\t" + undefinedFrameworks.mkString("\n\t")) - - def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name)) - val filtered0 = discovered.filter(includeTest).toList.distinct - val tests = if (orderedFilters.isEmpty) filtered0 else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).toList.distinct - val uniqueTests = distinctBy(tests)(_.name) - new ProcessedOptions(uniqueTests, setup.toList, cleanup.toList, testListeners.toList) - } - - private[this] def distinctBy[T, K](in: Seq[T])(f: T => K): Seq[T] = - { - val seen = new collection.mutable.HashSet[K] - in.filter(t => seen.add(f(t))) - } - - def apply(frameworks: Map[TestFramework, Framework], testLoader: ClassLoader, runners: Map[TestFramework, Runner], discovered: Seq[TestDefinition], config: Execution, log: ManagedLogger): Task[Output] = - { - val o = processOptions(config, discovered, log) - testTask(testLoader, frameworks, runners, o.tests, o.setup, o.cleanup, log, o.testListeners, config) - } - - def testTask(loader: ClassLoader, frameworks: Map[TestFramework, Framework], runners: Map[TestFramework, Runner], tests: Seq[TestDefinition], - userSetup: Iterable[ClassLoader => Unit], userCleanup: Iterable[ClassLoader => Unit], - log: ManagedLogger, testListeners: Seq[TestReportListener], config: Execution): Task[Output] = - { - def fj(actions: Iterable[() => Unit]): Task[Unit] = nop.dependsOn(actions.toSeq.fork(_()): _*) - def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map { a => () => a(loader) } - - val (frameworkSetup, runnables, frameworkCleanup) = - TestFramework.testTasks(frameworks, runners, loader, tests, log, testListeners) - - val setupTasks = fj(partApp(userSetup) :+ frameworkSetup) - val mainTasks = - if (config.parallel) - makeParallel(loader, runnables, setupTasks, config.tags) //.toSeq.join - else - makeSerial(loader, runnables, setupTasks, config.tags) - val taggedMainTasks = mainTasks.tagw(config.tags: _*) - taggedMainTasks map processResults flatMap { results => - val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall)) - cleanupTasks map { _ => results } + for (option <- config.options) { + option match { + case Filter(include) => testFilters += include + case Filters(includes) => + if (orderedFilters.nonEmpty) sys.error("Cannot define multiple ordered test filters.") + else orderedFilters = includes + case Exclude(exclude) => excludeTestsSet ++= exclude + case Listeners(listeners) => testListeners ++= listeners + case Setup(setupFunction) => setup += setupFunction + case Cleanup(cleanupFunction) => cleanup += cleanupFunction + case a: Argument => // now handled by whatever constructs `runners` } } + + if (excludeTestsSet.nonEmpty) + log.debug(excludeTestsSet.mkString("Excluding tests: \n\t", "\n\t", "")) + if (undefinedFrameworks.nonEmpty) + log.warn( + "Arguments defined for test frameworks that are not present:\n\t" + undefinedFrameworks + .mkString("\n\t")) + + def includeTest(test: TestDefinition) = + !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name)) + val filtered0 = discovered.filter(includeTest).toList.distinct + val tests = + if (orderedFilters.isEmpty) filtered0 + else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).toList.distinct + val uniqueTests = distinctBy(tests)(_.name) + new ProcessedOptions(uniqueTests, setup.toList, cleanup.toList, testListeners.toList) + } + + private[this] def distinctBy[T, K](in: Seq[T])(f: T => K): Seq[T] = { + val seen = new collection.mutable.HashSet[K] + in.filter(t => seen.add(f(t))) + } + + def apply(frameworks: Map[TestFramework, Framework], + testLoader: ClassLoader, + runners: Map[TestFramework, Runner], + discovered: Seq[TestDefinition], + config: Execution, + log: ManagedLogger): Task[Output] = { + val o = processOptions(config, discovered, log) + testTask(testLoader, + frameworks, + runners, + o.tests, + o.setup, + o.cleanup, + log, + o.testListeners, + config) + } + + def testTask(loader: ClassLoader, + frameworks: Map[TestFramework, Framework], + runners: Map[TestFramework, Runner], + tests: Seq[TestDefinition], + userSetup: Iterable[ClassLoader => Unit], + userCleanup: Iterable[ClassLoader => Unit], + log: ManagedLogger, + testListeners: Seq[TestReportListener], + config: Execution): Task[Output] = { + def fj(actions: Iterable[() => Unit]): Task[Unit] = nop.dependsOn(actions.toSeq.fork(_()): _*) + def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map { a => () => + a(loader) + } + + val (frameworkSetup, runnables, frameworkCleanup) = + TestFramework.testTasks(frameworks, runners, loader, tests, log, testListeners) + + val setupTasks = fj(partApp(userSetup) :+ frameworkSetup) + val mainTasks = + if (config.parallel) + makeParallel(loader, runnables, setupTasks, config.tags) //.toSeq.join + else + makeSerial(loader, runnables, setupTasks, config.tags) + val taggedMainTasks = mainTasks.tagw(config.tags: _*) + taggedMainTasks map processResults flatMap { results => + val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall)) + cleanupTasks map { _ => + results + } + } + } type TestRunnable = (String, TestFunction) - private def createNestedRunnables(loader: ClassLoader, testFun: TestFunction, nestedTasks: Seq[TestTask]): Seq[(String, TestFunction)] = + private def createNestedRunnables(loader: ClassLoader, + testFun: TestFunction, + nestedTasks: Seq[TestTask]): Seq[(String, TestFunction)] = nestedTasks.view.zipWithIndex map { case (nt, idx) => val testFunDef = testFun.taskDef - (testFunDef.fullyQualifiedName, TestFramework.createTestFunction(loader, new TaskDef(testFunDef.fullyQualifiedName + "-" + idx, testFunDef.fingerprint, testFunDef.explicitlySpecified, testFunDef.selectors), testFun.runner, nt)) + (testFunDef.fullyQualifiedName, + TestFramework.createTestFunction(loader, + new TaskDef(testFunDef.fullyQualifiedName + "-" + idx, + testFunDef.fingerprint, + testFunDef.explicitlySpecified, + testFunDef.selectors), + testFun.runner, + nt)) } - def makeParallel(loader: ClassLoader, runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = + def makeParallel(loader: ClassLoader, + runnables: Iterable[TestRunnable], + setupTasks: Task[Unit], + tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = toTasks(loader, runnables.toSeq, tags).dependsOn(setupTasks) - def toTasks(loader: ClassLoader, runnables: Seq[TestRunnable], tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = { + def toTasks(loader: ClassLoader, + runnables: Seq[TestRunnable], + tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = { val tasks = runnables.map { case (name, test) => toTask(loader, name, test, tags) } tasks.join.map(_.foldLeft(Map.empty[String, SuiteResult]) { case (sum, e) => @@ -212,7 +267,10 @@ object Tests { }) } - def toTask(loader: ClassLoader, name: String, fun: TestFunction, tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = { + def toTask(loader: ClassLoader, + name: String, + fun: TestFunction, + tags: Seq[(Tag, Int)]): Task[Map[String, SuiteResult]] = { val base = task { (name, fun.apply()) } val taggedBase = base.tagw(tags: _*).tag(fun.tags.map(ConcurrentRestrictions.Tag(_)): _*) taggedBase flatMap { @@ -229,21 +287,24 @@ object Tests { } } - def makeSerial(loader: ClassLoader, runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag, Int)]): Task[List[(String, SuiteResult)]] = - { - @tailrec - def processRunnable(runnableList: List[TestRunnable], acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] = - runnableList match { - case hd :: rst => - val testFun = hd._2 - val (result, nestedTasks) = testFun.apply() - val nestedRunnables = createNestedRunnables(loader, testFun, nestedTasks) - processRunnable(nestedRunnables.toList ::: rst, (hd._1, result) :: acc) - case Nil => acc - } + def makeSerial(loader: ClassLoader, + runnables: Seq[TestRunnable], + setupTasks: Task[Unit], + tags: Seq[(Tag, Int)]): Task[List[(String, SuiteResult)]] = { + @tailrec + def processRunnable(runnableList: List[TestRunnable], + acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] = + runnableList match { + case hd :: rst => + val testFun = hd._2 + val (result, nestedTasks) = testFun.apply() + val nestedRunnables = createNestedRunnables(loader, testFun, nestedTasks) + processRunnable(nestedRunnables.toList ::: rst, (hd._1, result) :: acc) + case Nil => acc + } - task { processRunnable(runnables.toList, List.empty) } dependsOn (setupTasks) - } + task { processRunnable(runnables.toList, List.empty) } dependsOn (setupTasks) + } def processResults(results: Iterable[(String, SuiteResult)]): Output = Output(overall(results.map(_._2.result)), results.toMap, Iterable.empty) @@ -257,24 +318,34 @@ object Tests { def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] = if (results.isEmpty) - task { Output(TestResult.Passed, Map.empty, Nil) } - else if (parallel) + task { Output(TestResult.Passed, Map.empty, Nil) } else if (parallel) reduced(results.toIndexedSeq, { - case (Output(v1, m1, _), Output(v2, m2, _)) => Output(if (severity(v1) < severity(v2)) v2 else v1, m1 ++ m2, Iterable.empty) + case (Output(v1, m1, _), Output(v2, m2, _)) => + Output(if (severity(v1) < severity(v2)) v2 else v1, m1 ++ m2, Iterable.empty) }) else { - def sequence(tasks: List[Task[Output]], acc: List[Output]): Task[List[Output]] = tasks match { - case Nil => task(acc.reverse) - case hd :: tl => hd flatMap { out => sequence(tl, out :: acc) } - } + def sequence(tasks: List[Task[Output]], acc: List[Output]): Task[List[Output]] = + tasks match { + case Nil => task(acc.reverse) + case hd :: tl => + hd flatMap { out => + sequence(tl, out :: acc) + } + } sequence(results.toList, List()) map { ress => - val (rs, ms) = ress.unzip { e => (e.overall, e.events) } + val (rs, ms) = ress.unzip { e => + (e.overall, e.events) + } Output(overall(rs), ms reduce (_ ++ _), Iterable.empty) } } def overall(results: Iterable[TestResult]): TestResult = - ((TestResult.Passed: TestResult) /: results) { (acc, result) => if (severity(acc) < severity(result)) result else acc } - def discover(frameworks: Seq[Framework], analysis: CompileAnalysis, log: Logger): (Seq[TestDefinition], Set[String]) = + ((TestResult.Passed: TestResult) /: results) { (acc, result) => + if (severity(acc) < severity(result)) result else acc + } + def discover(frameworks: Seq[Framework], + analysis: CompileAnalysis, + log: Logger): (Seq[TestDefinition], Set[String]) = discover(frameworks flatMap TestFramework.getFingerprints, allDefs(analysis), log) def allDefs(analysis: CompileAnalysis) = analysis match { @@ -290,27 +361,37 @@ object Tests { all }.toSeq } - def discover(fingerprints: Seq[Fingerprint], definitions: Seq[Definition], log: Logger): (Seq[TestDefinition], Set[String]) = - { - val subclasses = fingerprints collect { case sub: SubclassFingerprint => (sub.superclassName, sub.isModule, sub) }; - val annotations = fingerprints collect { case ann: AnnotatedFingerprint => (ann.annotationName, ann.isModule, ann) }; - log.debug("Subclass fingerprints: " + subclasses) - log.debug("Annotation fingerprints: " + annotations) + def discover(fingerprints: Seq[Fingerprint], + definitions: Seq[Definition], + log: Logger): (Seq[TestDefinition], Set[String]) = { + val subclasses = fingerprints collect { + case sub: SubclassFingerprint => (sub.superclassName, sub.isModule, sub) + }; + val annotations = fingerprints collect { + case ann: AnnotatedFingerprint => (ann.annotationName, ann.isModule, ann) + }; + log.debug("Subclass fingerprints: " + subclasses) + log.debug("Annotation fingerprints: " + annotations) - def firsts[A, B, C](s: Seq[(A, B, C)]): Set[A] = s.map(_._1).toSet - def defined(in: Seq[(String, Boolean, Fingerprint)], names: Set[String], IsModule: Boolean): Seq[Fingerprint] = - in collect { case (name, IsModule, print) if names(name) => print } + def firsts[A, B, C](s: Seq[(A, B, C)]): Set[A] = s.map(_._1).toSet + def defined(in: Seq[(String, Boolean, Fingerprint)], + names: Set[String], + IsModule: Boolean): Seq[Fingerprint] = + in collect { case (name, IsModule, print) if names(name) => print } - def toFingerprints(d: Discovered): Seq[Fingerprint] = - defined(subclasses, d.baseClasses, d.isModule) ++ - defined(annotations, d.annotations, d.isModule) + def toFingerprints(d: Discovered): Seq[Fingerprint] = + defined(subclasses, d.baseClasses, d.isModule) ++ + defined(annotations, d.annotations, d.isModule) - val discovered = Discovery(firsts(subclasses), firsts(annotations))(definitions) - // TODO: To pass in correct explicitlySpecified and selectors - val tests = for ((df, di) <- discovered; fingerprint <- toFingerprints(di)) yield new TestDefinition(df.name, fingerprint, false, Array(new SuiteSelector)) - val mains = discovered collect { case (df, di) if di.hasMain => df.name } - (tests, mains.toSet) - } + val discovered = Discovery(firsts(subclasses), firsts(annotations))(definitions) + // TODO: To pass in correct explicitlySpecified and selectors + val tests = for ((df, di) <- discovered; fingerprint <- toFingerprints(di)) + yield new TestDefinition(df.name, fingerprint, false, Array(new SuiteSelector)) + val mains = discovered collect { case (df, di) if di.hasMain => df.name } + (tests, mains.toSet) + } } -final class TestsFailedException extends RuntimeException("Tests unsuccessful") with FeedbackProvidedException +final class TestsFailedException + extends RuntimeException("Tests unsuccessful") + with FeedbackProvidedException diff --git a/main-actions/src/main/scala/sbt/compiler/Eval.scala b/main-actions/src/main/scala/sbt/compiler/Eval.scala index c1538bdf9..b0cdd17da 100644 --- a/main-actions/src/main/scala/sbt/compiler/Eval.scala +++ b/main-actions/src/main/scala/sbt/compiler/Eval.scala @@ -26,7 +26,10 @@ final class EvalImports(val strings: Seq[(String, Int)], val srcName: String) * the module from that class loader. `generated` contains the compiled classes and cache files related * to the expression. The name of the auto-generated module wrapping the expression is `enclosingModule`. */ -final class EvalResult(val tpe: String, val getValue: ClassLoader => Any, val generated: Seq[File], val enclosingModule: String) +final class EvalResult(val tpe: String, + val getValue: ClassLoader => Any, + val generated: Seq[File], + val enclosingModule: String) /** * The result of evaluating a group of Scala definitions. The definitions are wrapped in an auto-generated, @@ -35,7 +38,10 @@ final class EvalResult(val tpe: String, val getValue: ClassLoader => Any, val ge * from the classpath that the definitions were compiled against. The list of vals with the requested types is `valNames`. * The values for these may be obtained by providing the parent class loader to `values` as is done with `loader`. */ -final class EvalDefinitions(val loader: ClassLoader => ClassLoader, val generated: Seq[File], val enclosingModule: String, val valNames: Seq[String]) { +final class EvalDefinitions(val loader: ClassLoader => ClassLoader, + val generated: Seq[File], + val enclosingModule: String, + val valNames: Seq[String]) { def values(parent: ClassLoader): Seq[Any] = { val module = getModule(enclosingModule, loader(parent)) for (n <- valNames) yield module.getClass.getMethod(n).invoke(module) @@ -44,26 +50,31 @@ final class EvalDefinitions(val loader: ClassLoader => ClassLoader, val generate final class EvalException(msg: String) extends RuntimeException(msg) // not thread safe, since it reuses a Global instance -final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Settings => Reporter, backing: Option[File]) { - def this(mkReporter: Settings => Reporter, backing: Option[File]) = this(Nil, IO.classLocationFile[Product] :: Nil, mkReporter, backing) +final class Eval(optionsNoncp: Seq[String], + classpath: Seq[File], + mkReporter: Settings => Reporter, + backing: Option[File]) { + def this(mkReporter: Settings => Reporter, backing: Option[File]) = + this(Nil, IO.classLocationFile[Product] :: Nil, mkReporter, backing) def this() = this(s => new ConsoleReporter(s), None) backing.foreach(IO.createDirectory) val classpathString = Path.makeString(classpath ++ backing.toList) val options = "-cp" +: classpathString +: optionsNoncp - lazy val settings = - { - val s = new Settings(println) - new CompilerCommand(options.toList, s) // this side-effects on Settings.. - s - } + lazy val settings = { + val s = new Settings(println) + new CompilerCommand(options.toList, s) // this side-effects on Settings.. + s + } lazy val reporter = mkReporter(settings) + /** * Subclass of Global which allows us to mutate currentRun from outside. * See for rationale https://issues.scala-lang.org/browse/SI-8794 */ - final class EvalGlobal(settings: Settings, reporter: Reporter) extends Global(settings, reporter) { + final class EvalGlobal(settings: Settings, reporter: Reporter) + extends Global(settings, reporter) { override def currentRun: Run = curRun var curRun: Run = null } @@ -78,116 +89,135 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se private[this] var toUnlinkLater = List[Symbol]() private[this] def unlink(sym: Symbol) = sym.owner.info.decls.unlink(sym) - def eval(expression: String, imports: EvalImports = noImports, tpeName: Option[String] = None, srcName: String = "", line: Int = DefaultStartLine): EvalResult = - { - val ev = new EvalType[String] { - def makeUnit = mkUnit(srcName, line, expression) - def unlink = true - def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = { - val (parser, tree) = parse(unit, settingErrorStrings, _.expr()) - val tpt: Tree = expectedType(tpeName) - augment(parser, importTrees, tree, tpt, moduleName) - } - def extra(run: Run, unit: CompilationUnit) = enteringPhase(run.typerPhase.next) { (new TypeExtractor).getType(unit.body) } - def read(file: File) = IO.read(file) - def write(value: String, f: File) = IO.write(f, value) - def extraHash = "" + def eval(expression: String, + imports: EvalImports = noImports, + tpeName: Option[String] = None, + srcName: String = "", + line: Int = DefaultStartLine): EvalResult = { + val ev = new EvalType[String] { + def makeUnit = mkUnit(srcName, line, expression) + def unlink = true + def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = { + val (parser, tree) = parse(unit, settingErrorStrings, _.expr()) + val tpt: Tree = expectedType(tpeName) + augment(parser, importTrees, tree, tpt, moduleName) } - val i = evalCommon(expression :: Nil, imports, tpeName, ev) - val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl)) - new EvalResult(i.extra, value, i.generated, i.enclosingModule) + def extra(run: Run, unit: CompilationUnit) = enteringPhase(run.typerPhase.next) { + (new TypeExtractor).getType(unit.body) + } + def read(file: File) = IO.read(file) + def write(value: String, f: File) = IO.write(f, value) + def extraHash = "" } - def evalDefinitions(definitions: Seq[(String, scala.Range)], imports: EvalImports, srcName: String, file: Option[File], valTypes: Seq[String]): EvalDefinitions = - { - require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.") - val ev = new EvalType[Seq[String]] { - lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions) - def makeUnit = fullUnit - def unlink = false - def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = { - val fullParser = new syntaxAnalyzer.UnitParser(unit) - val trees = defUnits flatMap parseDefinitions - syntheticModule(fullParser, importTrees, trees.toList, moduleName) - } - def extra(run: Run, unit: CompilationUnit) = { - enteringPhase(run.typerPhase.next) { (new ValExtractor(valTypes.toSet)).getVals(unit.body) } - } - def read(file: File) = IO.readLines(file) - def write(value: Seq[String], file: File) = IO.writeLines(file, value) - def extraHash = file match { - case Some(f) => f.getAbsolutePath - case None => "" + val i = evalCommon(expression :: Nil, imports, tpeName, ev) + val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl)) + new EvalResult(i.extra, value, i.generated, i.enclosingModule) + } + def evalDefinitions(definitions: Seq[(String, scala.Range)], + imports: EvalImports, + srcName: String, + file: Option[File], + valTypes: Seq[String]): EvalDefinitions = { + require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.") + val ev = new EvalType[Seq[String]] { + lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions) + def makeUnit = fullUnit + def unlink = false + def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = { + val fullParser = new syntaxAnalyzer.UnitParser(unit) + val trees = defUnits flatMap parseDefinitions + syntheticModule(fullParser, importTrees, trees.toList, moduleName) + } + def extra(run: Run, unit: CompilationUnit) = { + enteringPhase(run.typerPhase.next) { + (new ValExtractor(valTypes.toSet)).getVals(unit.body) } } - val i = evalCommon(definitions.map(_._1), imports, Some(""), ev) - new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra) + def read(file: File) = IO.readLines(file) + def write(value: Seq[String], file: File) = IO.writeLines(file, value) + def extraHash = file match { + case Some(f) => f.getAbsolutePath + case None => "" + } + } + val i = evalCommon(definitions.map(_._1), imports, Some(""), ev) + new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra) + } + + private[this] def evalCommon[T](content: Seq[String], + imports: EvalImports, + tpeName: Option[String], + ev: EvalType[T]): EvalIntermediate[T] = { + import Eval._ + // TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting + // is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous + // value on the classpath when compiling. + val hash = Hash.toHex( + Hash(bytes( + stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) :: + seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes( + tpeName)(bytes) :: + bytes(ev.extraHash) :: Nil))) + val moduleName = makeModuleName(hash) + + lazy val unit = { + reporter.reset + ev.makeUnit + } + lazy val run = new Run { + override def units = (unit :: Nil).iterator + } + def unlinkAll(): Unit = + for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym + + val (extra, loader) = backing match { + case Some(back) if classExists(back, moduleName) => + val loader = (parent: ClassLoader) => new URLClassLoader(Array(back.toURI.toURL), parent) + val extra = ev.read(cacheFile(back, moduleName)) + (extra, loader) + case _ => + try { compileAndLoad(run, unit, imports, backing, moduleName, ev) } finally { unlinkAll() } } - private[this] def evalCommon[T](content: Seq[String], imports: EvalImports, tpeName: Option[String], ev: EvalType[T]): EvalIntermediate[T] = - { - import Eval._ - // TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting - // is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous - // value on the classpath when compiling. - val hash = Hash.toHex(Hash(bytes(stringSeqBytes(content) :: optBytes(backing)(fileExistsBytes) :: stringSeqBytes(options) :: - seqBytes(classpath)(fileModifiedBytes) :: stringSeqBytes(imports.strings.map(_._1)) :: optBytes(tpeName)(bytes) :: - bytes(ev.extraHash) :: Nil))) - val moduleName = makeModuleName(hash) - - lazy val unit = { - reporter.reset - ev.makeUnit - } - lazy val run = new Run { - override def units = (unit :: Nil).iterator - } - def unlinkAll(): Unit = for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym - - val (extra, loader) = backing match { - case Some(back) if classExists(back, moduleName) => - val loader = (parent: ClassLoader) => new URLClassLoader(Array(back.toURI.toURL), parent) - val extra = ev.read(cacheFile(back, moduleName)) - (extra, loader) - case _ => - try { compileAndLoad(run, unit, imports, backing, moduleName, ev) } - finally { unlinkAll() } - } - - val generatedFiles = getGeneratedFiles(backing, moduleName) - new EvalIntermediate(extra, loader, generatedFiles, moduleName) - } + val generatedFiles = getGeneratedFiles(backing, moduleName) + new EvalIntermediate(extra, loader, generatedFiles, moduleName) + } // location of the cached type or definition information - private[this] def cacheFile(base: File, moduleName: String): File = new File(base, moduleName + ".cache") - private[this] def compileAndLoad[T](run: Run, unit: CompilationUnit, imports: EvalImports, backing: Option[File], moduleName: String, ev: EvalType[T]): (T, ClassLoader => ClassLoader) = - { - global.curRun = run - run.currentUnit = unit - val dir = outputDirectory(backing) - settings.outputDirs setSingleOutput dir + private[this] def cacheFile(base: File, moduleName: String): File = + new File(base, moduleName + ".cache") + private[this] def compileAndLoad[T](run: Run, + unit: CompilationUnit, + imports: EvalImports, + backing: Option[File], + moduleName: String, + ev: EvalType[T]): (T, ClassLoader => ClassLoader) = { + global.curRun = run + run.currentUnit = unit + val dir = outputDirectory(backing) + settings.outputDirs setSingleOutput dir - val importTrees = parseImports(imports) - unit.body = ev.unitBody(unit, importTrees, moduleName) + val importTrees = parseImports(imports) + unit.body = ev.unitBody(unit, importTrees, moduleName) - def compile(phase: Phase): Unit = - { - globalPhase = phase - if (phase == null || phase == phase.next || reporter.hasErrors) - () - else { - enteringPhase(phase) { phase.run } - compile(phase.next) - } - } - - compile(run.namerPhase) - checkError("Type error in expression") - - val extra = ev.extra(run, unit) - for (f <- backing) ev.write(extra, cacheFile(f, moduleName)) - val loader = (parent: ClassLoader) => new AbstractFileClassLoader(dir, parent) - (extra, loader) + def compile(phase: Phase): Unit = { + globalPhase = phase + if (phase == null || phase == phase.next || reporter.hasErrors) + () + else { + enteringPhase(phase) { phase.run } + compile(phase.next) + } } + compile(run.namerPhase) + checkError("Type error in expression") + + val extra = ev.extra(run, unit) + for (f <- backing) ev.write(extra, cacheFile(f, moduleName)) + val loader = (parent: ClassLoader) => new AbstractFileClassLoader(dir, parent) + (extra, loader) + } + private[this] def expectedType(tpeName: Option[String]): Tree = tpeName match { case Some(tpe) => parseType(tpe) @@ -195,43 +225,55 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se } private[this] def outputDirectory(backing: Option[File]): AbstractFile = - backing match { case None => new VirtualDirectory("", None); case Some(dir) => new PlainFile(dir) } + backing match { + case None => new VirtualDirectory("", None); case Some(dir) => new PlainFile(dir) + } - def load(dir: AbstractFile, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new AbstractFileClassLoader(dir, parent)) - def loadPlain(dir: File, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent)) + def load(dir: AbstractFile, moduleName: String): ClassLoader => Any = + parent => getValue[Any](moduleName, new AbstractFileClassLoader(dir, parent)) + def loadPlain(dir: File, moduleName: String): ClassLoader => Any = + parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent)) //wrap tree in object objectName { def WrapValName = } - def augment(parser: global.syntaxAnalyzer.UnitParser, imports: Seq[Tree], tree: Tree, tpt: Tree, objectName: String): Tree = - { - val method = DefDef(NoMods, newTermName(WrapValName), Nil, Nil, tpt, tree) - syntheticModule(parser, imports, method :: Nil, objectName) - } - private[this] def syntheticModule(parser: global.syntaxAnalyzer.UnitParser, imports: Seq[Tree], definitions: List[Tree], objectName: String): Tree = - { - val emptyTypeName = nme.EMPTY.toTypeName - def emptyPkg = parser.atPos(0, 0, 0) { Ident(nme.EMPTY_PACKAGE_NAME) } - def emptyInit = DefDef( - NoMods, - nme.CONSTRUCTOR, - Nil, - List(Nil), - TypeTree(), - Block(List(Apply(Select(Super(This(emptyTypeName), emptyTypeName), nme.CONSTRUCTOR), Nil)), Literal(Constant(()))) - ) + def augment(parser: global.syntaxAnalyzer.UnitParser, + imports: Seq[Tree], + tree: Tree, + tpt: Tree, + objectName: String): Tree = { + val method = DefDef(NoMods, newTermName(WrapValName), Nil, Nil, tpt, tree) + syntheticModule(parser, imports, method :: Nil, objectName) + } + private[this] def syntheticModule(parser: global.syntaxAnalyzer.UnitParser, + imports: Seq[Tree], + definitions: List[Tree], + objectName: String): Tree = { + val emptyTypeName = nme.EMPTY.toTypeName + def emptyPkg = parser.atPos(0, 0, 0) { Ident(nme.EMPTY_PACKAGE_NAME) } + def emptyInit = DefDef( + NoMods, + nme.CONSTRUCTOR, + Nil, + List(Nil), + TypeTree(), + Block(List(Apply(Select(Super(This(emptyTypeName), emptyTypeName), nme.CONSTRUCTOR), Nil)), + Literal(Constant(()))) + ) - def moduleBody = Template(List(gen.scalaAnyRefConstr), noSelfType, emptyInit :: definitions) - def moduleDef = ModuleDef(NoMods, newTermName(objectName), moduleBody) - parser.makePackaging(0, emptyPkg, (imports :+ moduleDef).toList) - } + def moduleBody = Template(List(gen.scalaAnyRefConstr), noSelfType, emptyInit :: definitions) + def moduleDef = ModuleDef(NoMods, newTermName(objectName), moduleBody) + parser.makePackaging(0, emptyPkg, (imports :+ moduleDef).toList) + } private[this] final class TypeExtractor extends Traverser { private[this] var result = "" def getType(t: Tree) = { result = ""; traverse(t); result } override def traverse(tree: Tree): Unit = tree match { - case d: DefDef if d.symbol.nameString == WrapValName => result = d.symbol.tpe.finalResultType.toString - case _ => super.traverse(tree) + case d: DefDef if d.symbol.nameString == WrapValName => + result = d.symbol.tpe.finalResultType.toString + case _ => super.traverse(tree) } } + /** Tree traverser that obtains the names of vals in a top-level module whose type is a subtype of one of `types`.*/ private[this] final class ValExtractor(tpes: Set[String]) extends Traverser { private[this] var vals = List[String]() @@ -242,15 +284,20 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se } } override def traverse(tree: Tree): Unit = tree match { - case ValDef(_, n, actualTpe, _) if isTopLevelModule(tree.symbol.owner) && isAcceptableType(actualTpe.tpe) => + case ValDef(_, n, actualTpe, _) + if isTopLevelModule(tree.symbol.owner) && isAcceptableType(actualTpe.tpe) => vals ::= n.dropLocal.encoded case _ => super.traverse(tree) } } // inlined implemented of Symbol.isTopLevelModule that was removed in e5b050814deb2e7e1d6d05511d3a6cb6b013b549 - private[this] def isTopLevelModule(s: Symbol): Boolean = s.hasFlag(reflect.internal.Flags.MODULE) && s.owner.isPackageClass + private[this] def isTopLevelModule(s: Symbol): Boolean = + s.hasFlag(reflect.internal.Flags.MODULE) && s.owner.isPackageClass - private[this] final class EvalIntermediate[T](val extra: T, val loader: ClassLoader => ClassLoader, val generated: Seq[File], val enclosingModule: String) + private[this] final class EvalIntermediate[T](val extra: T, + val loader: ClassLoader => ClassLoader, + val generated: Seq[File], + val enclosingModule: String) private[this] def classExists(dir: File, name: String) = (new File(dir, name + ".class")).exists // TODO: use the code from Analyzer @@ -264,7 +311,10 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se (s contains moduleName) } - private[this] class ParseErrorStrings(val base: String, val extraBlank: String, val missingBlank: String, val extraSemi: String) + private[this] class ParseErrorStrings(val base: String, + val extraBlank: String, + val missingBlank: String, + val extraSemi: String) private[this] def definitionErrorStrings = new ParseErrorStrings( base = "Error parsing definition.", extraBlank = " Ensure that there are no blank lines within a definition.", @@ -275,67 +325,67 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se base = "Error parsing expression.", extraBlank = " Ensure that there are no blank lines within a setting.", missingBlank = " Ensure that settings are separated by blank lines.", - extraSemi = " Note that settings are expressions and do not end with semicolons. (Semicolons are fine within {} blocks, however.)" + extraSemi = + " Note that settings are expressions and do not end with semicolons. (Semicolons are fine within {} blocks, however.)" ) /** * Parses the provided compilation `unit` according to `f` and then performs checks on the final parser state * to catch errors that are common when the content is embedded in a blank-line-delimited format. */ - private[this] def parse[T](unit: CompilationUnit, errors: ParseErrorStrings, f: syntaxAnalyzer.UnitParser => T): (syntaxAnalyzer.UnitParser, T) = - { - val parser = new syntaxAnalyzer.UnitParser(unit) + private[this] def parse[T](unit: CompilationUnit, + errors: ParseErrorStrings, + f: syntaxAnalyzer.UnitParser => T): (syntaxAnalyzer.UnitParser, T) = { + val parser = new syntaxAnalyzer.UnitParser(unit) - val tree = f(parser) - val extra = parser.in.token match { - case EOF => errors.extraBlank - case _ => "" - } - checkError(errors.base + extra) - - parser.accept(EOF) - val extra2 = parser.in.token match { - case SEMI => errors.extraSemi - case NEWLINE | NEWLINES => errors.missingBlank - case _ => "" - } - checkError(errors.base + extra2) - - (parser, tree) + val tree = f(parser) + val extra = parser.in.token match { + case EOF => errors.extraBlank + case _ => "" } - private[this] def parseType(tpe: String): Tree = - { - val tpeParser = new syntaxAnalyzer.UnitParser(mkUnit("", DefaultStartLine, tpe)) - val tpt0: Tree = tpeParser.typ() - tpeParser.accept(EOF) - checkError("Error parsing expression type.") - tpt0 + checkError(errors.base + extra) + + parser.accept(EOF) + val extra2 = parser.in.token match { + case SEMI => errors.extraSemi + case NEWLINE | NEWLINES => errors.missingBlank + case _ => "" } + checkError(errors.base + extra2) + + (parser, tree) + } + private[this] def parseType(tpe: String): Tree = { + val tpeParser = new syntaxAnalyzer.UnitParser(mkUnit("", DefaultStartLine, tpe)) + val tpt0: Tree = tpeParser.typ() + tpeParser.accept(EOF) + checkError("Error parsing expression type.") + tpt0 + } private[this] def parseImports(imports: EvalImports): Seq[Tree] = imports.strings flatMap { case (s, line) => parseImport(mkUnit(imports.srcName, line, s)) } - private[this] def parseImport(importUnit: CompilationUnit): Seq[Tree] = - { - val parser = new syntaxAnalyzer.UnitParser(importUnit) - val trees: Seq[Tree] = parser.importClause() - parser.accept(EOF) - checkError("Error parsing imports for expression.") - trees - } + private[this] def parseImport(importUnit: CompilationUnit): Seq[Tree] = { + val parser = new syntaxAnalyzer.UnitParser(importUnit) + val trees: Seq[Tree] = parser.importClause() + parser.accept(EOF) + checkError("Error parsing imports for expression.") + trees + } private[this] def parseDefinitions(du: CompilationUnit): Seq[Tree] = parse(du, definitionErrorStrings, parseDefinitions)._2 /** Parses one or more definitions (defs, vals, lazy vals, classes, traits, modules). */ - private[this] def parseDefinitions(parser: syntaxAnalyzer.UnitParser): Seq[Tree] = - { - val defs = ListBuffer[Tree]() - do { - defs ++= parser.nonLocalDefOrDcl - parser.acceptStatSepOpt() - } while (!parser.isStatSeqEnd) - defs.toList - } + private[this] def parseDefinitions(parser: syntaxAnalyzer.UnitParser): Seq[Tree] = { + val defs = ListBuffer[Tree]() + do { + defs ++= parser.nonLocalDefOrDcl + parser.acceptStatSepOpt() + } while (!parser.isStatSeqEnd) + defs.toList + } private[this] trait EvalType[T] { + /** Extracts additional information after the compilation unit is evaluated.*/ def extra(run: Run, unit: CompilationUnit): T @@ -369,47 +419,55 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se val DefaultStartLine = 0 private[this] def makeModuleName(hash: String): String = "$" + Hash.halve(hash) private[this] def noImports = new EvalImports(Nil, "") - private[this] def mkUnit(srcName: String, firstLine: Int, s: String) = new CompilationUnit(new EvalSourceFile(srcName, firstLine, s)) - private[this] def checkError(label: String) = if (reporter.hasErrors) throw new EvalException(label) + private[this] def mkUnit(srcName: String, firstLine: Int, s: String) = + new CompilationUnit(new EvalSourceFile(srcName, firstLine, s)) + private[this] def checkError(label: String) = + if (reporter.hasErrors) throw new EvalException(label) - private[this] final class EvalSourceFile(name: String, startLine: Int, contents: String) extends BatchSourceFile(name, contents) { + private[this] final class EvalSourceFile(name: String, startLine: Int, contents: String) + extends BatchSourceFile(name, contents) { override def lineToOffset(line: Int): Int = super.lineToOffset((line - startLine) max 0) override def offsetToLine(offset: Int): Int = super.offsetToLine(offset) + startLine } + /** * Constructs a CompilationUnit for each definition, which can be used to independently parse the definition into a Tree. * Additionally, a CompilationUnit for the combined definitions is constructed for use by combined compilation after parsing. */ - private[this] def mkDefsUnit(srcName: String, definitions: Seq[(String, scala.Range)]): (CompilationUnit, Seq[CompilationUnit]) = - { - def fragmentUnit(content: String, lineMap: Array[Int]) = new CompilationUnit(fragmentSourceFile(srcName, content, lineMap)) + private[this] def mkDefsUnit( + srcName: String, + definitions: Seq[(String, scala.Range)]): (CompilationUnit, Seq[CompilationUnit]) = { + def fragmentUnit(content: String, lineMap: Array[Int]) = + new CompilationUnit(fragmentSourceFile(srcName, content, lineMap)) - import collection.mutable.ListBuffer - val lines = new ListBuffer[Int]() - val defs = new ListBuffer[CompilationUnit]() - val fullContent = new java.lang.StringBuilder() - for ((defString, range) <- definitions) { - defs += fragmentUnit(defString, range.toArray) - fullContent.append(defString) - lines ++= range - fullContent.append("\n\n") - lines ++= (range.end :: range.end :: Nil) - } - val fullUnit = fragmentUnit(fullContent.toString, lines.toArray) - (fullUnit, defs.toSeq) + import collection.mutable.ListBuffer + val lines = new ListBuffer[Int]() + val defs = new ListBuffer[CompilationUnit]() + val fullContent = new java.lang.StringBuilder() + for ((defString, range) <- definitions) { + defs += fragmentUnit(defString, range.toArray) + fullContent.append(defString) + lines ++= range + fullContent.append("\n\n") + lines ++= (range.end :: range.end :: Nil) } + val fullUnit = fragmentUnit(fullContent.toString, lines.toArray) + (fullUnit, defs.toSeq) + } /** * Source file that can map the offset in the file to and from line numbers that may discontinuous. * The values in `lineMap` must be ordered, but need not be consecutive. */ - private[this] def fragmentSourceFile(srcName: String, content: String, lineMap: Array[Int]) = new BatchSourceFile(srcName, content) { - override def lineToOffset(line: Int): Int = super.lineToOffset(lineMap.indexWhere(_ == line) max 0) - override def offsetToLine(offset: Int): Int = index(lineMap, super.offsetToLine(offset)) - // the SourceFile attribute is populated from this method, so we are required to only return the name - override def toString = new File(srcName).getName - private[this] def index(a: Array[Int], i: Int): Int = if (i < 0 || i >= a.length) 0 else a(i) - } + private[this] def fragmentSourceFile(srcName: String, content: String, lineMap: Array[Int]) = + new BatchSourceFile(srcName, content) { + override def lineToOffset(line: Int): Int = + super.lineToOffset(lineMap.indexWhere(_ == line) max 0) + override def offsetToLine(offset: Int): Int = index(lineMap, super.offsetToLine(offset)) + // the SourceFile attribute is populated from this method, so we are required to only return the name + override def toString = new File(srcName).getName + private[this] def index(a: Array[Int], i: Int): Int = if (i < 0 || i >= a.length) 0 else a(i) + } } private[sbt] object Eval { def optBytes[T](o: Option[T])(f: T => Array[Byte]): Array[Byte] = seqBytes(o.toSeq)(f) @@ -417,7 +475,8 @@ private[sbt] object Eval { def seqBytes[T](s: Seq[T])(f: T => Array[Byte]): Array[Byte] = bytes(s map f) def bytes(b: Seq[Array[Byte]]): Array[Byte] = bytes(b.length) ++ b.flatten.toArray[Byte] def bytes(b: Boolean): Array[Byte] = Array[Byte](if (b) 1 else 0) - def filesModifiedBytes(fs: Array[File]): Array[Byte] = if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes) + def filesModifiedBytes(fs: Array[File]): Array[Byte] = + if (fs eq null) filesModifiedBytes(Array[File]()) else seqBytes(fs)(fileModifiedBytes) def fileModifiedBytes(f: File): Array[Byte] = (if (f.isDirectory) filesModifiedBytes(f listFiles classDirFilter) else bytes(f.lastModified)) ++ bytes(f.getAbsolutePath) @@ -426,18 +485,16 @@ private[sbt] object Eval { bytes(f.getAbsolutePath) def bytes(s: String): Array[Byte] = s getBytes "UTF-8" - def bytes(l: Long): Array[Byte] = - { - val buffer = ByteBuffer.allocate(8) - buffer.putLong(l) - buffer.array - } - def bytes(i: Int): Array[Byte] = - { - val buffer = ByteBuffer.allocate(4) - buffer.putInt(i) - buffer.array - } + def bytes(l: Long): Array[Byte] = { + val buffer = ByteBuffer.allocate(8) + buffer.putLong(l) + buffer.array + } + def bytes(i: Int): Array[Byte] = { + val buffer = ByteBuffer.allocate(4) + buffer.putInt(i) + buffer.array + } /** The name of the synthetic val in the synthetic module that an expression is assigned to. */ final val WrapValName = "$sbtdef" @@ -446,20 +503,18 @@ private[sbt] object Eval { * Gets the value of the expression wrapped in module `objectName`, which is accessible via `loader`. * The module name should not include the trailing `$`. */ - def getValue[T](objectName: String, loader: ClassLoader): T = - { - val module = getModule(objectName, loader) - val accessor = module.getClass.getMethod(WrapValName) - val value = accessor.invoke(module) - value.asInstanceOf[T] - } + def getValue[T](objectName: String, loader: ClassLoader): T = { + val module = getModule(objectName, loader) + val accessor = module.getClass.getMethod(WrapValName) + val value = accessor.invoke(module) + value.asInstanceOf[T] + } /** Gets the top-level module `moduleName` from the provided class `loader`. The module name should not include the trailing `$`.*/ - def getModule(moduleName: String, loader: ClassLoader): Any = - { - val clazz = Class.forName(moduleName + "$", true, loader) - clazz.getField("MODULE$").get(null) - } + def getModule(moduleName: String, loader: ClassLoader): Any = { + val clazz = Class.forName(moduleName + "$", true, loader) + clazz.getField("MODULE$").get(null) + } private val classDirFilter: FileFilter = DirectoryFilter || GlobFilter("*.class") } diff --git a/main-actions/src/test/scala/sbt/CacheIvyTest.scala b/main-actions/src/test/scala/sbt/CacheIvyTest.scala index eb41444ed..e37d0d6b5 100644 --- a/main-actions/src/test/scala/sbt/CacheIvyTest.scala +++ b/main-actions/src/test/scala/sbt/CacheIvyTest.scala @@ -31,15 +31,19 @@ class CacheIvyTest extends Properties("CacheIvy") { content = converter.toJsonUnsafe(value) } - private def testCache[T: JsonFormat, U](f: (SingletonCache[T], CacheStore) => U)(implicit cache: SingletonCache[T]): U = { + private def testCache[T: JsonFormat, U](f: (SingletonCache[T], CacheStore) => U)( + implicit cache: SingletonCache[T]): U = { val store = new InMemoryStore(Converter) f(cache, store) } - private def cachePreservesEquality[T: JsonFormat](m: T, eq: (T, T) => Prop, str: T => String): Prop = testCache[T, Prop] { (cache, store) => - cache.write(store, m) - val out = cache.read(store) - eq(out, m) :| s"Expected: ${str(m)}" :| s"Got: ${str(out)}" + private def cachePreservesEquality[T: JsonFormat](m: T, + eq: (T, T) => Prop, + str: T => String): Prop = testCache[T, Prop] { + (cache, store) => + cache.write(store, m) + val out = cache.read(store) + eq(out, m) :| s"Expected: ${str(m)}" :| s"Got: ${str(out)}" } implicit val arbExclusionRule: Arbitrary[ExclusionRule] = Arbitrary( @@ -77,11 +81,22 @@ class CacheIvyTest extends Properties("CacheIvy") { inclusions <- Gen.listOf(arbitrary[InclusionRule]) extraAttributes <- Gen.mapOf(arbitrary[(String, String)]) crossVersion <- arbitrary[CrossVersion] - } yield ModuleID( - organization = o, name = n, revision = r, configurations = cs, isChanging = isChanging, isTransitive = isTransitive, - isForce = isForce, explicitArtifacts = explicitArtifacts.toVector, inclusions = inclusions.toVector, exclusions = exclusions.toVector, - extraAttributes = extraAttributes, crossVersion = crossVersion, branchName = branch - ) + } yield + ModuleID( + organization = o, + name = n, + revision = r, + configurations = cs, + isChanging = isChanging, + isTransitive = isTransitive, + isForce = isForce, + explicitArtifacts = explicitArtifacts.toVector, + inclusions = inclusions.toVector, + exclusions = exclusions.toVector, + extraAttributes = extraAttributes, + crossVersion = crossVersion, + branchName = branch + ) } property("moduleIDFormat") = forAll { (m: ModuleID) => diff --git a/main-actions/src/test/scala/sbt/compiler/EvalTest.scala b/main-actions/src/test/scala/sbt/compiler/EvalTest.scala index e12c8839e..31eaceb60 100644 --- a/main-actions/src/test/scala/sbt/compiler/EvalTest.scala +++ b/main-actions/src/test/scala/sbt/compiler/EvalTest.scala @@ -16,22 +16,23 @@ class EvalTest extends Properties("eval") { property("inferred integer") = forAll { (i: Int) => val result = eval.eval(i.toString) (label("Value", value(result)) |: (value(result) == i)) && - (label("Type", value(result)) |: (result.tpe == IntType)) && - (label("Files", result.generated) |: (result.generated.isEmpty)) + (label("Type", value(result)) |: (result.tpe == IntType)) && + (label("Files", result.generated) |: (result.generated.isEmpty)) } property("explicit integer") = forAll { (i: Int) => val result = eval.eval(i.toString, tpeName = Some(IntType)) (label("Value", value(result)) |: (value(result) == i)) && - (label("Type", result.tpe) |: (result.tpe == IntType)) && - (label("Files", result.generated) |: (result.generated.isEmpty)) + (label("Type", result.tpe) |: (result.tpe == IntType)) && + (label("Files", result.generated) |: (result.generated.isEmpty)) } property("type mismatch") = forAll { (i: Int, l: Int) => val line = math.abs(l) val src = "mismatch" - throws(classOf[RuntimeException])(eval.eval(i.toString, tpeName = Some(BooleanType), line = line, srcName = src)) && - hasErrors(line + 1, src) + throws(classOf[RuntimeException])( + eval.eval(i.toString, tpeName = Some(BooleanType), line = line, srcName = src)) && + hasErrors(line + 1, src) } property("backed local class") = forAll { (i: Int) => @@ -40,8 +41,8 @@ class EvalTest extends Properties("eval") { val result = eval.eval(local(i)) val v = value(result).asInstanceOf[{ def i: Int }].i (label("Value", v) |: (v == i)) && - (label("Type", result.tpe) |: (result.tpe == LocalType)) && - (label("Files", result.generated) |: result.generated.nonEmpty) + (label("Type", result.tpe) |: (result.tpe == LocalType)) && + (label("Files", result.generated) |: result.generated.nonEmpty) } } @@ -62,35 +63,38 @@ val p = { property("val test") = secure { val defs = (ValTestContent, 1 to 7) :: Nil - val res = eval.evalDefinitions(defs, new EvalImports(Nil, ""), "", None, "scala.Int" :: Nil) + val res = + eval.evalDefinitions(defs, new EvalImports(Nil, ""), "", None, "scala.Int" :: Nil) label("Val names", res.valNames) |: (res.valNames.toSet == ValTestNames) } property("explicit import") = forAll(testImport("import math.abs" :: Nil)) property("wildcard import") = forAll(testImport("import math._" :: Nil)) - property("comma-separated imports") = forAll(testImport("import annotation._, math._, meta._" :: Nil)) - property("multiple imports") = forAll(testImport("import annotation._" :: "import math._" :: "import meta._" :: Nil)) + property("comma-separated imports") = forAll( + testImport("import annotation._, math._, meta._" :: Nil)) + property("multiple imports") = forAll( + testImport("import annotation._" :: "import math._" :: "import meta._" :: Nil)) - private[this] def testImport(imports: Seq[String]): Int => Prop = i => - value(eval.eval("abs(" + i + ")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs(i) + private[this] def testImport(imports: Seq[String]): Int => Prop = + i => + value(eval.eval("abs(" + i + ")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs( + i) private[this] def local(i: Int) = "{ class ETest(val i: Int); new ETest(" + i + ") }" val LocalType = "AnyRef{val i: Int}" private[this] def value(r: EvalResult) = r.getValue(getClass.getClassLoader) - private[this] def hasErrors(line: Int, src: String) = - { - val is = reporter.infos - ("Has errors" |: is.nonEmpty) && - all(is.toSeq.map(validPosition(line, src)): _*) - } - private[this] def validPosition(line: Int, src: String)(i: Info) = - { - val nme = i.pos.source.file.name - (label("Severity", i.severity) |: (i.severity == ERROR)) && - (label("Line", i.pos.line) |: (i.pos.line == line)) && - (label("Name", nme) |: (nme == src)) - } + private[this] def hasErrors(line: Int, src: String) = { + val is = reporter.infos + ("Has errors" |: is.nonEmpty) && + all(is.toSeq.map(validPosition(line, src)): _*) + } + private[this] def validPosition(line: Int, src: String)(i: Info) = { + val nme = i.pos.source.file.name + (label("Severity", i.severity) |: (i.severity == ERROR)) && + (label("Line", i.pos.line) |: (i.pos.line == line)) && + (label("Name", nme) |: (nme == src)) + } val IntType = "Int" val BooleanType = "Boolean" diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index f944cb5f6..6d7b6c52e 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -16,7 +16,9 @@ object BasicCommandStrings { /** The command name to terminate the program.*/ val TerminateAction: String = Exit - def helpBrief = (HelpCommand, s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand ').") + def helpBrief = + (HelpCommand, + s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand ').") def helpDetailed = s"""$HelpCommand Prints a help summary. @@ -30,26 +32,29 @@ $HelpCommand Searches the help according to the provided regular expression. """ - def CompletionsDetailed = "Displays a list of completions for the given argument string (run 'completions ')." + def CompletionsDetailed = + "Displays a list of completions for the given argument string (run 'completions ')." def CompletionsBrief = (CompletionsCommand, CompletionsDetailed) def templateBrief = (TemplateCommand, "Creates a new sbt build.") def templateDetailed = TemplateCommand + """ [--options]