diff --git a/build.sbt b/build.sbt index 0975b7d88..3ebd1cbdf 100644 --- a/build.sbt +++ b/build.sbt @@ -108,7 +108,9 @@ lazy val sbtRoot: Project = (project in file(".")) Transform.conscriptSettings(bundledLauncherProj), publish := {}, publishLocal := {}, - skip in publish := true + skip in publish := true, + commands in Global += Command.single("sbtOn")((state, dir) => + s"sbtProj/test:runMain sbt.RunFromSourceMain $dir" :: state), ) // This is used to configure an sbt-launcher for this version of sbt. @@ -429,28 +431,38 @@ lazy val mainProj = (project in file("main")) // 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") + .enablePlugins(BuildInfoPlugin) .settings( baseSettings, name := "sbt", normalizedName := "sbt", crossScalaVersions := Seq(baseScalaVersion), crossPaths := false, + javaOptions ++= Seq("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"), mimaSettings, - mimaBinaryIssueFilters ++= Vector( - // Added more items to Import trait. - exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$WatchSource_="), - exclude[ReversedMissingMethodProblem]("sbt.Import.WatchSource"), - - // Dropped in favour of kind-projector's polymorphic lambda literals - exclude[DirectMissingMethodProblem]("sbt.Import.Param"), - exclude[DirectMissingMethodProblem]("sbt.package.Param"), - - // Dropped in favour of plain scala.Function, and its compose method - exclude[DirectMissingMethodProblem]("sbt.package.toFn1"), - ) + mimaBinaryIssueFilters ++= sbtIgnoredProblems, + addBuildInfoToConfig(Test), + buildInfoObject in Test := "TestBuildInfo", + buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), + connectInput in run in Test := true, ) .configure(addSbtCompilerBridge) +lazy val sbtIgnoredProblems = { + Vector( + // Added more items to Import trait. + exclude[ReversedMissingMethodProblem]("sbt.Import.sbt$Import$_setter_$WatchSource_="), + exclude[ReversedMissingMethodProblem]("sbt.Import.WatchSource"), + + // Dropped in favour of kind-projector's polymorphic lambda literals + exclude[DirectMissingMethodProblem]("sbt.Import.Param"), + exclude[DirectMissingMethodProblem]("sbt.package.Param"), + + // Dropped in favour of plain scala.Function, and its compose method + exclude[DirectMissingMethodProblem]("sbt.package.toFn1"), + ) +} + def runNpm(command: String, base: File, log: sbt.internal.util.ManagedLogger) = { val npm = if (sbt.internal.util.Util.isWindows) "npm.cmd" else "npm" import scala.sys.process._ diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index ec61346cb..bd69e4c30 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -13,6 +13,7 @@ import sbt.internal.inc.Analysis import TaskExtra._ import sbt.internal.util.FeedbackProvidedException import xsbti.api.Definition +import xsbti.api.ClassLike import xsbti.compile.CompileAnalysis import ConcurrentRestrictions.Tag @@ -389,7 +390,11 @@ object Tests { defined(subclasses, d.baseClasses, d.isModule) ++ defined(annotations, d.annotations, d.isModule) - val discovered = Discovery(firsts(subclasses), firsts(annotations))(definitions) + val discovered = Discovery(firsts(subclasses), firsts(annotations))(definitions.filter { + case c: ClassLike => + c.topLevel + case _ => false + }) // 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)) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d40467dff..1f2dcfd3a 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -327,11 +327,13 @@ object Defaults extends BuildCommon { excludeFilter in unmanagedSources).value, watchSources in ConfigGlobal ++= { val baseDir = baseDirectory.value - val bases = unmanagedSourceDirectories.value ++ (if (sourcesInBase.value) Seq(baseDir) - else Seq.empty) + val bases = unmanagedSourceDirectories.value val include = (includeFilter in unmanagedSources).value val exclude = (excludeFilter in unmanagedSources).value - bases.map(b => new Source(b, include, exclude)) + val baseSources = + if (sourcesInBase.value) Seq(new Source(baseDir, include, exclude, recursive = false)) + else Nil + bases.map(b => new Source(b, include, exclude)) ++ baseSources }, managedSourceDirectories := Seq(sourceManaged.value), managedSources := generate(sourceGenerators).value, @@ -777,21 +779,24 @@ object Defaults extends BuildCommon { } def intlStamp(c: String, analysis: Analysis, s: Set[String]): Long = { if (s contains c) Long.MinValue - else { - val x = { - import analysis.{ relations => rel, apis } - rel.internalClassDeps(c).map(intlStamp(_, analysis, s + c)) ++ - rel.externalDeps(c).map(stamp) + - (apis.internal.get(c) match { - case Some(x) => x.compilationTimestamp - case _ => Long.MinValue - }) - }.max - if (x != Long.MinValue) { - stamps(c) = x - } - x - } + else + stamps.getOrElse( + c, { + val x = { + import analysis.{ relations => rel, apis } + rel.internalClassDeps(c).map(intlStamp(_, analysis, s + c)) ++ + rel.externalDeps(c).map(stamp) + + (apis.internal.get(c) match { + case Some(x) => x.compilationTimestamp + case _ => Long.MinValue + }) + }.max + if (x != Long.MinValue) { + stamps(c) = x + } + x + } + ) } def noSuccessYet(test: String) = succeeded.get(test) match { case None => true diff --git a/main/src/main/scala/sbt/internal/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala index 7183caefb..9abec04c6 100644 --- a/main/src/main/scala/sbt/internal/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -109,6 +109,14 @@ object LogManager { } } + // to change from global being the default to overriding, switch the order of state.get and data.get + def getOr[T](key: AttributeKey[T], + data: Settings[Scope], + scope: Scope, + state: State, + default: T): T = + data.get(scope, key) orElse state.get(key) getOrElse default + // This is the main function that is used to generate the logger for tasks. def defaultLogger( data: Settings[Scope], @@ -125,13 +133,10 @@ object LogManager { val execId: Option[String] = execOpt flatMap { _.execId } val log = LogExchange.logger(loggerName, channelName, execId) val scope = task.scope - // to change from global being the default to overriding, switch the order of state.get and data.get - def getOr[T](key: AttributeKey[T], default: T): T = - data.get(scope, key) orElse state.get(key) getOrElse default - val screenLevel = getOr(logLevel.key, Level.Info) - val backingLevel = getOr(persistLogLevel.key, Level.Debug) - val screenTrace = getOr(traceLevel.key, defaultTraceLevel(state)) - val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue) + val screenLevel = getOr(logLevel.key, data, scope, state, Level.Info) + val backingLevel = getOr(persistLogLevel.key, data, scope, state, Level.Debug) + val screenTrace = getOr(traceLevel.key, data, scope, state, defaultTraceLevel(state)) + val backingTrace = getOr(persistTraceLevel.key, data, scope, state, Int.MaxValue) val extraBacked = state.globalLogging.backed :: relay :: Nil val consoleOpt = consoleLocally(state, console) val config = MainAppender.MainAppenderConfig( @@ -188,6 +193,9 @@ object LogManager { relay: Appender, extra: List[Appender] ): ManagedLogger = { + val scope = task.scope + val screenLevel = getOr(logLevel.key, data, scope, state, Level.Info) + val backingLevel = getOr(persistLogLevel.key, data, scope, state, Level.Debug) val execOpt = state.currentCommand val loggerName: String = s"bg-${task.key.label}-${generateId.incrementAndGet}" val channelName: Option[String] = execOpt flatMap (_.source map (_.channelName)) @@ -197,7 +205,7 @@ object LogManager { val consoleOpt = consoleLocally(state, console) LogExchange.bindLoggerAppenders( loggerName, - (consoleOpt.toList map { _ -> Level.Debug }) ::: (relay -> Level.Debug) :: Nil) + (consoleOpt.toList map { _ -> screenLevel }) ::: (relay -> backingLevel) :: Nil) log } diff --git a/main/src/main/scala/sbt/internal/parser/SbtParser.scala b/main/src/main/scala/sbt/internal/parser/SbtParser.scala index a3b9b321b..6e89cdf28 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -116,7 +116,9 @@ private[sbt] object SbtParser { scalacGlobalInitReporter = Some(new ConsoleReporter(settings)) // Mix Positions, otherwise global ignores -Yrangepos - val global = new Global(settings, globalReporter) with Positions + val global = new Global(settings, globalReporter) with Positions { + override protected def synchronizeNames = true // https://github.com/scala/bug/issues/10605 + } val run = new global.Run // Add required dummy unit for initialization... val initFile = new BatchSourceFile("", "") diff --git a/notes/0.13.17/addSbtPlugin-cross.markdown b/notes/0.13.17/addSbtPlugin-cross.markdown deleted file mode 100644 index aba7194a4..000000000 --- a/notes/0.13.17/addSbtPlugin-cross.markdown +++ /dev/null @@ -1,7 +0,0 @@ -### Bug fixes - -- Fixes `addSbtPlugin` to use the correct version of sbt. [#3393][]/[#3397][] by [@dwijnand][] - - [#3393]: https://github.com/sbt/sbt/issues/3393 - [#3397]: https://github.com/sbt/sbt/pull/3397 - [@dwijnand]: http://github.com/dwijnand diff --git a/notes/1.0.3.markdown b/notes/1.0.3.markdown new file mode 100644 index 000000000..06624a85a --- /dev/null +++ b/notes/1.0.3.markdown @@ -0,0 +1,61 @@ +This is a hotfix release for sbt 1.0.x series. + +### Bug fixes + +- Fixes `~` recompiling in loop (when a source generator or sbt-buildinfo is present). [#3501][3501]/[#3634][3634] by [@dwijnand][@dwijnand] +- Fixes undercompilation on inheritance on same source. [zinc#424][zinc424] by [@eed3si9n][@eed3si9n] +- Fixes the compilation of package-protected objects. [zinc#431][zinc431] by [@jvican][@jvican] +- Workaround for Java returning `null` for `getGenericParameterTypes`. [zinc#446][zinc446] by [@jvican][@jvican] +- Fixes test detection regression. sbt 1.0.3 filters out nested objects/classes from the list, restoring compatibility with 0.13. [#3669][3669] by [@cunei][@cunei] +- Uses Scala 2.12.4 for the build definition. This includes fix for runtime reflection of empty package members under Java 9. [#3587][3587] by [@eed3si9n][@eed3si9n] +- Fixes extra `/` in Ivy style patterns. [lm#170][lm170] by [@laughedelic][@laughedelic] +- Fixes "destination file exist" error message by including the file name. [lm171][lm171] by [@leonardehrenfried][@leonardehrenfried] +- Fixes JDK 9 warning "Illegal reflective access" in library management module and Ivy. [lm173][lm173] by [@dwijnand][@dwijnand] + +### Improvements + +- Adds `sbt.watch.mode` system property to allow switching back to old polling behaviour for watch. See below for more details. + +#### Alternative watch mode + +sbt 1.0.0 introduced a new mechanism for watching for source changes based on the NIO `WatchService` in Java 1.7. On +some platforms (namely macOS) this has led to long delays before changes are picked up. An alternative `WatchService` +for these platforms is planned for sbt 1.1.0 ([#3527][3527]), in the meantime an option to select which watch service +has been added. + +The new `sbt.watch.mode` JVM flag has been added with the following supported values: + +- `polling`: (default for macOS) poll the filesystem for changes (mechanism used in sbt 0.13). +- `nio` (default for other platforms): use the NIO based `WatchService`. + +If you are experiencing long delays on a non-macOS machine then try adding `-Dsbt.watch.mode=polling` to your sbt +options. + +[#3597][3597] by [@stringbean][@stringbean] + +### Contributors + +A huge thank you to everyone who's helped improve sbt and Zinc 1 by using them, reporting bugs, improving our documentation, porting builds, porting plugins, and submitting and reviewing pull requests. + +This release was brought to you by 15 contributors, according to `git shortlog -sn --no-merges v1.0.2..v1.0.3` on sbt, zinc, librarymanagement, util, io, and website: Eugene Yokota, Dale Wijnand, Michael Stringer, Jorge Vicente Cantero (jvican), Alexey Alekhin, Antonio Cunei, Andrey Artemov, Jeffrey Olchovy, Kenji Yoshida (xuwei-k), Dominik Winter, Long Jinwei, Arnout Engelen, Justin Kaeser, Leonard Ehrenfried, Sakib Hadžiavdić. Thank you! + + [@dwijnand]: https://github.com/dwijnand + [@cunei]: https://github.com/cunei + [@eed3si9n]: https://github.com/eed3si9n + [@jvican]: https://github.com/jvican + [@stringbean]: https://github.com/stringbean + [@laughedelic]: https://github.com/laughedelic + [@leonardehrenfried]: https://github.com/leonardehrenfried + [3669]: https://github.com/sbt/sbt/pull/3669 + [3583]: https://github.com/sbt/sbt/issues/3583 + [3587]: https://github.com/sbt/sbt/issues/3587 + [3527]: https://github.com/sbt/sbt/issues/3527 + [3597]: https://github.com/sbt/sbt/pull/3597 + [3501]: https://github.com/sbt/sbt/issues/3501 + [3634]: https://github.com/sbt/sbt/pull/3634 + [lm170]: https://github.com/sbt/librarymanagement/pull/170 + [lm171]: https://github.com/sbt/librarymanagement/pull/171 + [lm173]: https://github.com/sbt/librarymanagement/pull/173 + [zinc424]: https://github.com/sbt/zinc/pull/424 + [zinc431]: https://github.com/sbt/zinc/pull/431 + [zinc446]: https://github.com/sbt/zinc/pull/446 diff --git a/notes/1.0.3/watch.md b/notes/1.0.3/watch.md deleted file mode 100644 index ff751fdf9..000000000 --- a/notes/1.0.3/watch.md +++ /dev/null @@ -1,26 +0,0 @@ -### Fixes with compatibility implications - -### Improvements - -- Add `sbt.watch.mode` system property to allow switching back to old polling behaviour for watch. See below for more details. [#3597][3597] by [@stringbean][@stringbean] - -### Bug fixes - -#### Alternative watch mode - -sbt 1.0.0 introduced a new mechanism for watching for source changes based on the NIO `WatchService` in Java 1.7. On -some platforms (namely macOS) this has led to long delays before changes are picked up. An alternative `WatchService` -for these platforms is planned for sbt 1.1.0 ([#3527][3527]), in the meantime an option to select which watch service -has been added. - -The new `sbt.watch.mode` JVM flag has been added with the following supported values: - -- `polling`: (default for macOS) poll the filesystem for changes (mechanism used in sbt 0.13). -- `nio` (default for other platforms): use the NIO based `WatchService`. - -If you are experiencing long delays on a non-macOS machine then try adding `-Dsbt.watch.mode=polling` to your sbt -options. - -[@stringbean]: https://github.com/stringbean -[3527]: https://github.com/sbt/sbt/issues/3527 -[3597]: https://github.com/sbt/sbt/pull/3597 diff --git a/notes/1.0.4.markdown b/notes/1.0.4.markdown new file mode 100644 index 000000000..280abf7e0 --- /dev/null +++ b/notes/1.0.4.markdown @@ -0,0 +1,51 @@ +This is a hotfix release for sbt 1.0.x series. + +### Bug fixes + +- Fixes undercompilation of value classes when the underlying type changes. [zinc#444][zinc444] by [@smarter][@smarter] +- Fixes `ArrayIndexOutOfBoundsException` on Ivy when running on Java 9. [ivy#27][ivy27] by [@xuwei-k][@xuwei-k] +- Fixes Java 9 warning by upgrading to launcher 1.0.2. [ivy#26][ivy26]/[launcher#45][launcher45] by [@dwijnand][@dwijnand] +- Fixes `run` outputing debug level logs. [#3655][3655]/[#3717][3717] by [@cunei][@cunei] +- Fixes performance regression caused by classpath hashing. [zinc#452][zinc452] by [@jvican][@jvican] +- Fixes performance regression of `testQuick`. [#3680][3680]/[#3720][3720] by [@OlegYch][@OlegYch] +- Disables Ivy log4j caller location calculation for performance regression reported in [#3711][3711]. [util#132][util132] by [@leonardehrenfried][@leonardehrenfried] +- Works around Scala compiler's `templateStats()` not being thread-safe. [#3743][3743] by [@cunei][@cunei] +- Fixes "Attempting to overwrite" error message. [lm#174][lm174] by [@dwijnand][@dwijnand] +- Fixes incorrect eviction warning message. [lm#179][lm179] by [@xuwei-k][@xuwei-k] +- Registers Ivy protocol only for `http:` and `https:` to be more plugin friendly. [lm183][lm183] by [@tpunder][@tpunder] + +### Enhancement + +- Adds Scala 2.13.0-M2 support. [zinc#453][zinc453] by [@eed3si9n][@eed3si9n] and [@jan0sch][@jan0sch] + +### Internal + +- Improves Zinc scripted testing. [zinc440][zinc#440] by [@jvican][@jvican] + + [@dwijnand]: https://github.com/dwijnand + [@cunei]: https://github.com/cunei + [@eed3si9n]: https://github.com/eed3si9n + [@jvican]: https://github.com/jvican + [@OlegYch]: https://github.com/OlegYch + [@leonardehrenfried]: https://github.com/leonardehrenfried + [@xuwei-k]: https://github.com/xuwei-k + [@tpunder]: https://github.com/tpunder + [@smarter]: https://github.com/smarter + [@jan0sch]: https://github.com/jan0sch + [3655]: https://github.com/sbt/sbt/issues/3655 + [3717]: https://github.com/sbt/sbt/pull/3717 + [ivy26]: https://github.com/sbt/ivy/pull/26 + [ivy27]: https://github.com/sbt/ivy/pull/27 + [launcher45]: https://github.com/sbt/launcher/pull/45 + [3680]: https://github.com/sbt/sbt/issues/3680 + [3720]: https://github.com/sbt/sbt/pull/3720 + [3743]: https://github.com/sbt/sbt/pull/3743 + [3711]: https://github.com/sbt/sbt/issues/3711 + [util132]: https://github.com/sbt/util/pull/132 + [lm174]: https://github.com/sbt/librarymanagement/pull/174 + [lm179]: https://github.com/sbt/librarymanagement/pull/179 + [lm183]: https://github.com/sbt/librarymanagement/pull/183 + [zinc452]: https://github.com/sbt/zinc/pull/452 + [zinc444]: https://github.com/sbt/zinc/pull/444 + [zinc453]: https://github.com/sbt/zinc/pull/453 + [zinc440]: https://github.com/sbt/zinc/pull/440 diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9a0339a1e..ee3a894fb 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,16 +6,16 @@ object Dependencies { val scala282 = "2.8.2" val scala292 = "2.9.2" val scala293 = "2.9.3" - val scala210 = "2.10.6" - val scala211 = "2.11.8" - val scala212 = "2.12.3" + val scala210 = "2.10.7" + val scala211 = "2.11.12" + val scala212 = "2.12.4" val baseScalaVersion = scala212 // sbt modules private val ioVersion = "1.1.0" - private val utilVersion = "1.0.2" - private val lmVersion = "1.0.2" - private val zincVersion = "1.0.2" + private val utilVersion = "1.0.3" + private val lmVersion = "1.0.4" + private val zincVersion = "1.0.5" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion @@ -30,8 +30,8 @@ object Dependencies { private val libraryManagementCore = "org.scala-sbt" %% "librarymanagement-core" % lmVersion private val libraryManagementIvy = "org.scala-sbt" %% "librarymanagement-ivy" % lmVersion - val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.0" - val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.0" + val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.2" + val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.2" val testInterface = "org.scala-sbt" % "test-interface" % "1.0" private val compilerInterface = "org.scala-sbt" % "compiler-interface" % zincVersion diff --git a/project/Scripted.scala b/project/Scripted.scala index 33e23936e..932d55a0f 100644 --- a/project/Scripted.scala +++ b/project/Scripted.scala @@ -108,6 +108,8 @@ object Scripted { prescripted: File => Unit, launchOpts: Seq[String]): Unit = { System.err.println(s"About to run tests: ${args.mkString("\n * ", "\n * ", "\n")}") + // Force Log4J to not use a thread context classloader otherwise it throws a CCE + sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true" val noJLine = new classpath.FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil) val loader = classpath.ClasspathUtilities.toLoader(scriptedSbtClasspath.files, noJLine) val bridgeClass = Class.forName("sbt.test.ScriptedRunner", true, loader) diff --git a/project/plugins.sbt b/project/plugins.sbt index f5433f7cf..4cfe0e774 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -12,3 +12,4 @@ addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.1") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0-M1") addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.14") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") diff --git a/sbt/src/sbt-test/tests/nested-tests/build.sbt b/sbt/src/sbt-test/tests/nested-tests/build.sbt new file mode 100644 index 000000000..2e47706f6 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-tests/build.sbt @@ -0,0 +1,8 @@ +libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.13.5" % "test" + +version := "0.0.1" +name := "broken" +organization := "org.catastrophe" +//scalaVersion := "2.10.6" +scalaVersion := "2.12.3" + diff --git a/sbt/src/sbt-test/tests/nested-tests/src/test/scala/q/X.scala b/sbt/src/sbt-test/tests/nested-tests/src/test/scala/q/X.scala new file mode 100644 index 000000000..6c80f0bce --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-tests/src/test/scala/q/X.scala @@ -0,0 +1,25 @@ +package q + +// +// On 1.0.3+ this test will say: +// [info] + Nesting.startsWith: OK, passed 100 tests. +// [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 +// +// On 1.0.0 to 1.0.2 it will crash with: +// [error] java.lang.ClassNotFoundException: q.X.Y$ +// + +import org.scalacheck.{Prop, Properties} +import Prop.forAll + +class U extends Properties("Nesting") +object X extends U { + property("startsWith") = forAll { (a: String, b: String) => + (a+b).startsWith(a) + } + object Y extends U { + property("endsWith") = forAll { (a: String, b: String) => + (a+b).endsWith(b) + } + } +} diff --git a/sbt/src/sbt-test/tests/nested-tests/test b/sbt/src/sbt-test/tests/nested-tests/test new file mode 100644 index 000000000..c8987ae90 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-tests/test @@ -0,0 +1,2 @@ +> test + diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala new file mode 100644 index 000000000..0394772b3 --- /dev/null +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -0,0 +1,110 @@ +package sbt + +import scala.annotation.tailrec + +import xsbti._ + +object RunFromSourceMain { + private val sbtVersion = "1.0.3" // "dev" + private val scalaVersion = "2.12.4" + + def main(args: Array[String]): Unit = args match { + case Array() => sys.error(s"Must specify working directory as the first argument") + case Array(wd, args @ _*) => run(file(wd), args) + } + + // this arrangement is because Scala does not always properly optimize away + // the tail recursion in a catch statement + @tailrec private def run(baseDir: File, args: Seq[String]): Unit = + runImpl(baseDir, args) match { + case Some((baseDir, args)) => run(baseDir, args) + case None => () + } + + private def runImpl(baseDir: File, args: Seq[String]): Option[(File, Seq[String])] = + try launch(getConf(baseDir, args)) map exit + catch { + case r: xsbti.FullReload => Some((baseDir, r.arguments())) + case scala.util.control.NonFatal(e) => e.printStackTrace(); errorAndExit(e.toString) + } + + @tailrec private def launch(conf: AppConfiguration): Option[Int] = + new xMain().run(conf) match { + case e: xsbti.Exit => Some(e.code) + case _: xsbti.Continue => None + case r: xsbti.Reboot => launch(getConf(conf.baseDirectory(), r.arguments())) + case x => handleUnknownMainResult(x) + } + + private val noGlobalLock = new GlobalLock { + def apply[T](lockFile: File, run: java.util.concurrent.Callable[T]) = run.call() + } + + private def getConf(baseDir: File, args: Seq[String]): AppConfiguration = new AppConfiguration { + def baseDirectory = baseDir + def arguments = args.toArray + def provider = new AppProvider { appProvider => + def scalaProvider = new ScalaProvider { scalaProvider => + def scalaOrg = "org.scala-lang" + def launcher = new Launcher { + def getScala(version: String) = getScala(version, "") + def getScala(version: String, reason: String) = getScala(version, reason, scalaOrg) + def getScala(version: String, reason: String, scalaOrg: String) = scalaProvider + def app(id: xsbti.ApplicationID, version: String) = appProvider + def topLoader = new java.net.URLClassLoader(Array(), null) + def globalLock = noGlobalLock + def bootDirectory = file(sys.props("user.home")) / ".sbt" / "boot" + def ivyRepositories = Array() + def appRepositories = Array() + def isOverrideRepositories = false + def ivyHome = file(sys.props("user.home")) / ".ivy2" + def checksums = Array("sha1", "md5") + } + def version = scalaVersion + def libDir: File = launcher.bootDirectory / s"scala-$version" / "lib" + def jar(name: String): File = libDir / s"$name.jar" + def libraryJar = jar("scala-library") + def compilerJar = jar("scala-compiler") + def jars = libDir.listFiles(f => !f.isDirectory && f.getName.endsWith(".jar")) + def loader = new java.net.URLClassLoader(jars map (_.toURI.toURL), null) + def app(id: xsbti.ApplicationID) = appProvider + } + + def id = ApplicationID( + "org.scala-sbt", + "sbt", + sbtVersion, + "sbt.xMain", + Seq("xsbti", "extra"), + CrossValue.Disabled, + Nil + ) + + def mainClasspath = + buildinfo.TestBuildInfo.fullClasspath.iterator + .map(s => file(s.stripPrefix("Attributed(").stripSuffix(")"))) + .toArray + + def loader = new java.net.URLClassLoader(mainClasspath map (_.toURI.toURL), null) + def entryPoint = classOf[xMain] + def mainClass = classOf[xMain] + def newMain = new xMain + + def components = new ComponentProvider { + def componentLocation(id: String) = ??? + def component(componentID: String) = ??? + def defineComponent(componentID: String, components: Array[File]) = ??? + def addToComponent(componentID: String, components: Array[File]) = ??? + def lockFile = ??? + } + } + } + + private def handleUnknownMainResult(x: MainResult): Nothing = { + val clazz = if (x eq null) "" else " (class: " + x.getClass + ")" + errorAndExit("Invalid main result: " + x + clazz) + } + + private def errorAndExit(msg: String): Nothing = { System.err.println(msg); exit(1) } + private def exit(code: Int): Nothing = System.exit(code).asInstanceOf[Nothing] +}