From 9c32c16adee85e8ae5032ebb5140c34c50664053 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 23 Nov 2017 14:58:26 +0000 Subject: [PATCH] Create RunFromSourceMain & "sbtOn" command --- build.sbt | 9 ++ project/plugins.sbt | 1 + .../test/scala/sbt/RunFromSourceMain.scala | 110 ++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 sbt/src/test/scala/sbt/RunFromSourceMain.scala diff --git a/build.sbt b/build.sbt index 7e4e6d751..0c07705a6 100644 --- a/build.sbt +++ b/build.sbt @@ -376,17 +376,26 @@ 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 ++= sbtIgnoredProblems, + addBuildInfoToConfig(Test), + buildInfoObject in Test := "TestBuildInfo", + buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile), + connectInput in run in Test := true, ) .configure(addSbtCompilerBridge) +commands in Global += Command.single("sbtOn")((state, dir) => + s"sbtProj/test:runMain sbt.RunFromSourceMain $dir" :: state) + lazy val sbtIgnoredProblems = { Seq( // Added more items to Import trait. diff --git a/project/plugins.sbt b/project/plugins.sbt index 2eec0f9bd..2a33ba504 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -11,3 +11,4 @@ addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0-M1") addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.10") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") 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] +}