Static launcher, get bridge sources from resources

This commit introduces a new "static" launcher that does not use Ivy to
gather all the artifacts that it requires, but rather expect them to be
immediately available.

To be able to use sbt without Internet access, we add a new
`ComponentCompiler` that is able to retrieve the bridge sources from the
resources on classpath and compile it.
This commit is contained in:
Martin Duhem 2016-04-06 11:52:51 +02:00
parent cb4500f085
commit b8472668ff
11 changed files with 522 additions and 18 deletions

View File

@ -28,7 +28,24 @@ def commonSettings: Seq[Setting[_]] = Seq(
incOptions := incOptions.value.withNameHashing(true),
crossScalaVersions := Seq(scala210),
bintrayPackage := (bintrayPackage in ThisBuild).value,
bintrayRepository := (bintrayRepository in ThisBuild).value
bintrayRepository := (bintrayRepository in ThisBuild).value,
test in assembly := {},
assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = true),
assemblyMergeStrategy in assembly := {
case PathList(ps @ _*) if ps.last == "javax.inject.Named" => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith ".class" => MergeStrategy.first
case PathList(ps @ _*) if ps.last endsWith "module.properties" => MergeStrategy.first
case PathList(ps @ _*) if ps.last == "MANIFEST.MF" => MergeStrategy.rename
case "LICENSE" => MergeStrategy.first
case "NOTICE" => MergeStrategy.first
// excluded from fat jar because otherwise we may pick it up when determining the `actualVersion`
// of other scala instances.
case "compiler.properties" => MergeStrategy.discard
case x =>
val oldStrategy = (assemblyMergeStrategy in assembly).value
oldStrategy(x)
}
)
def minimalSettings: Seq[Setting[_]] =
@ -346,11 +363,24 @@ lazy val compilerIntegrationProj = (project in (compilePath / "integration")).
name := "Compiler Integration"
)
lazy val packageBridgeSource = settingKey[Boolean]("Whether to package the compiler bridge sources in compiler ivy project's resources.")
lazy val compilerIvyProj = (project in compilePath / "ivy").
dependsOn (ivyProj, compilerProj).
settings(
baseSettings,
name := "Compiler Ivy Integration"
name := "Compiler Ivy Integration",
packageBridgeSource := false,
resourceGenerators in Compile <+= Def.task {
if (packageBridgeSource.value) {
val compilerBridgeSrc = (Keys.packageSrc in (compileInterfaceProj, Compile)).value
val xsbtiJAR = (Keys.packageBin in (interfaceProj, Compile)).value
// They are immediately used by the static launcher.
val included = Set("scala-compiler.jar", "scala-library.jar")
val scalaJars = (externalDependencyClasspath in Compile).value.map(_.data).filter(j => included contains j.getName)
Seq(compilerBridgeSrc, xsbtiJAR) ++ scalaJars
}
else Nil
}
)
lazy val scriptedBaseProj = (project in scriptedPath / "base").
@ -594,5 +624,16 @@ def customCommands: Seq[Setting[_]] = Seq(
"publish" ::
"bintrayRelease" ::
state
},
// Produces a fat runnable JAR that contains everything needed to use sbt.
commands += Command.command("install") { state =>
val packageBridgeSourceKey = packageBridgeSource.key.label
val compilerIvy = compilerIvyProj.id
val sbt = sbtProj.id
s"$compilerIvy/clean" ::
s"set $packageBridgeSourceKey in $compilerIvy := true" ::
s"$sbt/assembly" ::
s"set $packageBridgeSourceKey in $compilerIvy := false" ::
state
}
)

View File

@ -0,0 +1,21 @@
package sbt
package compiler
/**
* Base trait for the different means of retrieving the compiler bridge sources
*/
sealed trait CompilerBridgeProvider
/**
* Indicates that the compiler bridge should be retrieved using Ivy.
* @param ivyConfiguration The `sbt.IvyConfiguration` to use to retrieve the sources.
* @param module The module that contains the sources of the compiler bridge.
*/
final case class IvyBridgeProvider(ivyConfiguration: IvyConfiguration, module: ModuleID) extends CompilerBridgeProvider
/**
* Indicates that the compiler bridge sould be retrieved from the resources on classpath.
* @param sourceJarName The name of the JAR containing the bridge sources, to find in the resources.
* @param reflectJarName The name of the JAR corresponding to `scala-reflect.jar` in the standard scala distribution.
*/
final case class ResourceBridgeProvider(sourceJarName: String, reflectJarName: String) extends CompilerBridgeProvider

View File

@ -15,7 +15,7 @@ object ComponentCompiler {
val compilerInterfaceSrcID = compilerInterfaceID + srcExtension
val javaVersion = System.getProperty("java.class.version")
@deprecated("Use `interfaceProvider(ComponentManager, IvyConfiguration, ModuleID)`.", "0.13.10")
@deprecated("Use `interfaceProvider(ComponentManager, CompilerBridgeProvider)`.", "0.13.12")
def interfaceProvider(manager: ComponentManager): CompilerInterfaceProvider = new CompilerInterfaceProvider {
def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File =
{
@ -26,13 +26,21 @@ object ComponentCompiler {
}
}
def interfaceProvider(manager: ComponentManager, ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID): CompilerInterfaceProvider = new CompilerInterfaceProvider {
@deprecated("Use `interfaceProvider(ComponentManager, CompilerBridgeProvider)`", "0.13.12")
def interfaceProvider(manager: ComponentManager, ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID): CompilerInterfaceProvider =
interfaceProvider(manager, IvyBridgeProvider(ivyConfiguration, sourcesModule))
def interfaceProvider(manager: ComponentManager, compilerBridgeProvider: CompilerBridgeProvider): CompilerInterfaceProvider = new CompilerInterfaceProvider {
def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File =
{
// this is the instance used to compile the interface component
val componentCompiler = new IvyComponentCompiler(new RawCompiler(scalaInstance, ClasspathOptions.auto, log), manager, ivyConfiguration, sourcesModule, log)
log.debug("Getting " + sourcesModule + " from component compiler for Scala " + scalaInstance.version)
componentCompiler()
compilerBridgeProvider match {
case IvyBridgeProvider(ivyConfiguration, sourcesModule) =>
val componentCompiler = new IvyComponentCompiler(new RawCompiler(scalaInstance, ClasspathOptions.auto, log), manager, ivyConfiguration, sourcesModule, log)
log.debug("Getting " + sourcesModule + " from component compiler for Scala " + scalaInstance.version)
componentCompiler()
case ResourceBridgeProvider(sourceJarName, reflectJarName) =>
val componentCompiler = new ResourceComponentCompiler(new RawCompiler(scalaInstance, ClasspathOptions.auto, log), manager, sourceJarName, reflectJarName, log)
log.debug("Compiling bridge source from resources for Scala " + scalaInstance.version)
componentCompiler()
}
}
}
@ -41,7 +49,7 @@ object ComponentCompiler {
* The compiled classes are cached using the provided component manager according
* to the actualVersion field of the RawCompiler.
*/
@deprecated("Replaced by IvyComponentCompiler.", "0.13.10")
@deprecated("Replaced by IvyComponentCompiler and ResourceComponentCompiler.", "0.13.12")
class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager) {
import ComponentCompiler._
def apply(id: String): File =
@ -79,6 +87,59 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager) {
}
}
/**
* Compiles the compiler bridge using the source extracted from the resources on classpath.
*/
private[compiler] class ResourceComponentCompiler(compiler: RawCompiler, manager: ComponentManager, sourceJarName: String, reflectJarName: String, log: Logger) {
import ComponentCompiler._
private val reflectID = "reflect"
def apply(): File = {
val binID = "bridge-from-resource" + binSeparator + compiler.scalaInstance.actualVersion + "__" + javaVersion
manager.file(binID)(new IfMissing.Define(true, compileAndInstall(binID)))
}
private def copyFromResources(destinationDirectory: File, fileName: String): File = {
Option(getClass.getClassLoader.getResourceAsStream(sourceJarName)) match {
case Some(stream) =>
val copiedFile = new File(destinationDirectory, fileName)
val out = new java.io.FileOutputStream(copiedFile)
var read = 0
val content = new Array[Byte](1024)
while ({ read = stream.read(content); read != -1 }) {
out.write(content, 0, read)
}
copiedFile
case None =>
throw new InvalidComponent(s"Could not find '$fileName' on resources path.")
}
}
private def compileAndInstall(binID: String): Unit =
IO.withTemporaryDirectory { binaryDirectory =>
val targetJar = new File(binaryDirectory, s"$binID.jar")
val xsbtiJars = manager.files(xsbtiID)(IfMissing.Fail)
IO.withTemporaryDirectory { tempDirectory =>
val sourceJar = copyFromResources(tempDirectory, sourceJarName)
val reflectJar = copyFromResources(tempDirectory, reflectJarName)
// We need to have `scala-reflect.jar` on the classpath when compiling the compiler bridge.
// In `IvyComponentCompiler`, `scala-reflect.jar` is automatically pulled in as a dependency.
AnalyzingCompiler.compileSources(Seq(sourceJar), targetJar, xsbtiJars ++ Seq(reflectJar), "bridge-from-resources", compiler, log)
manager.define(binID, Seq(targetJar))
}
}
}
/**
* Component compiler which is able to to retrieve the compiler bridge sources
* `sourceModule` using Ivy.

View File

@ -0,0 +1,192 @@
package sbt
import java.io.File
import java.net.URL
import org.apache.ivy.core.cache.ArtifactOrigin
import org.apache.ivy.core.cache.{ DefaultRepositoryCacheManager, RepositoryCacheManager }
import org.apache.ivy.core.module.descriptor.{ Artifact => IvyArtifact, DefaultArtifact, DefaultDependencyArtifactDescriptor, DefaultModuleDescriptor, DependencyArtifactDescriptor, DependencyDescriptor }
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.core.report.ArtifactDownloadReport
import org.apache.ivy.core.report.{ DownloadReport, DownloadStatus }
import org.apache.ivy.core.report.MetadataArtifactDownloadReport
import org.apache.ivy.core.resolve.{ DownloadOptions, ResolveData, ResolvedModuleRevision }
import org.apache.ivy.core.search.{ ModuleEntry, OrganisationEntry, RevisionEntry }
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.namespace.Namespace
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.plugins.resolver.{ DependencyResolver, ResolverSettings }
import org.apache.ivy.plugins.resolver.util.ResolvedResource
import FakeResolver._
/**
* A fake `DependencyResolver` that statically serves predefined artifacts.
*/
private[sbt] class FakeResolver(private var name: String, cacheDir: File, modules: ModulesMap) extends DependencyResolver {
private object Artifact {
def unapply(art: IvyArtifact): Some[(String, String, String)] = {
val revisionID = art.getModuleRevisionId()
val organisation = revisionID.getOrganisation
val name = revisionID.getName
val revision = revisionID.getRevision
Some((organisation, name, revision))
}
def unapply(dd: DependencyDescriptor): Some[(String, String, String)] = {
val module = dd.getDependencyId()
val organisation = module.getOrganisation
val name = module.getName
val mrid = dd.getDependencyRevisionId()
val revision = mrid.getRevision()
Some((organisation, name, revision))
}
}
override def publish(artifact: IvyArtifact, src: File, overwrite: Boolean): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def abortPublishTransaction(): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def beginPublishTransaction(module: ModuleRevisionId, overwrite: Boolean): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def commitPublishTransaction(): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def download(artifact: ArtifactOrigin, options: DownloadOptions): ArtifactDownloadReport = {
val report = new ArtifactDownloadReport(artifact.getArtifact)
val path = new URL(artifact.getLocation).toURI.getPath
assert(path.nonEmpty, "Path to local artifact is empty.")
val localFile = new File(path)
assert(localFile.exists, "Local file doesn't exist.")
report.setLocalFile(localFile)
report.setDownloadStatus(DownloadStatus.SUCCESSFUL)
report.setSize(localFile.length)
report
}
override def download(artifacts: Array[IvyArtifact], options: DownloadOptions): DownloadReport = {
val report = new DownloadReport
artifacts foreach { art =>
val artifactOrigin = locate(art)
report.addArtifactReport(download(artifactOrigin, options))
}
report
}
override def dumpSettings(): Unit = ()
override def exists(artifact: IvyArtifact): Boolean = {
val Artifact(organisation, name, revision) = artifact
modules.get((organisation, name, revision)).isDefined
}
// This is a fake resolver and we don't have Ivy files. Ivy's spec says we can return `null` if
// we can't find the module descriptor.
override def findIvyFileRef(dd: DependencyDescriptor, data: ResolveData): ResolvedResource = null
override def getDependency(dd: DependencyDescriptor, data: ResolveData): ResolvedModuleRevision = {
val Artifact(organisation, name, revision) = dd
val mrid = dd.getDependencyRevisionId()
val artifact = modules get ((organisation, name, revision)) map { arts =>
val artifacts: Array[DependencyArtifactDescriptor] = arts.toArray map (_ artifactOf dd)
val moduleDescriptor = DefaultModuleDescriptor.newDefaultInstance(mrid, artifacts)
val defaultArtifact = arts.headOption match {
case Some(FakeArtifact(name, tpe, ext, _)) => new DefaultArtifact(mrid, new java.util.Date, name, tpe, ext)
case None => null
}
val metadataReport = new MetadataArtifactDownloadReport(defaultArtifact)
metadataReport.setDownloadStatus(DownloadStatus.SUCCESSFUL)
new ResolvedModuleRevision(this, this, moduleDescriptor, metadataReport)
}
artifact getOrElse (throw new Exception(s"Could not find module $organisation % $name % $revision"))
}
override def getName(): String = name
override val getNamespace: Namespace = {
val ns = new Namespace()
ns.setName(name)
ns
}
override val getRepositoryCacheManager: RepositoryCacheManager = {
val cacheName = name + "-cache"
val ivySettings = new IvySettings()
val baseDir = cacheDir
new DefaultRepositoryCacheManager(cacheName, ivySettings, baseDir)
}
override def listModules(organisation: OrganisationEntry): Array[ModuleEntry] =
modules.keys.collect {
case (o, m, _) if o == organisation.getOrganisation =>
val organisationEntry = new OrganisationEntry(this, o)
new ModuleEntry(organisationEntry, m)
}.toArray
override def listOrganisations(): Array[OrganisationEntry] =
modules.keys.map { case (o, _, _) => new OrganisationEntry(this, o) }.toArray
override def listRevisions(module: ModuleEntry): Array[RevisionEntry] =
modules.keys.collect {
case (o, m, v) if o == module.getOrganisation && m == module.getModule =>
new RevisionEntry(module, v)
}.toArray
override def listTokenValues(tokens: Array[String], criteria: java.util.Map[_, _]): Array[java.util.Map[_, _]] =
Array.empty
override def listTokenValues(token: String, otherTokenValues: java.util.Map[_, _]): Array[String] =
Array.empty
override def locate(art: IvyArtifact): ArtifactOrigin = {
val Artifact(moduleOrganisation, moduleName, moduleRevision) = art
val artifact =
for {
artifacts <- modules get ((moduleOrganisation, moduleName, moduleRevision))
artifact <- artifacts find (a => a.name == art.getName && a.tpe == art.getType && a.ext == art.getExt)
} yield new ArtifactOrigin(art, /* isLocal = */ true, artifact.file.toURI.toURL.toString)
artifact getOrElse (throw new IllegalStateException(s"Asking for non-existing module: $moduleOrganisation % $moduleName % $moduleRevision"))
}
override def reportFailure(art: IvyArtifact): Unit = ()
override def reportFailure(): Unit = ()
override def setName(name: String): Unit = {
this.name = name
getNamespace.setName(name)
}
override def setSettings(settings: ResolverSettings): Unit = ()
private class LocalURLResource(jar: File) extends URLResource(jar.toURI.toURL) {
override def isLocal(): Boolean = true
}
}
private[sbt] object FakeResolver {
type ModulesMap = Map[(String, String, String), Seq[FakeArtifact]]
final case class FakeArtifact(name: String, tpe: String, ext: String, file: File) {
def artifactOf(dd: DependencyDescriptor): DependencyArtifactDescriptor =
new DefaultDependencyArtifactDescriptor(dd, name, tpe, ext, file.toURI.toURL, new java.util.HashMap)
}
}

View File

@ -163,6 +163,11 @@ final case class SftpRepository(name: String, connection: SshConnection, pattern
protected def copy(patterns: Patterns): SftpRepository = SftpRepository(name, connection, patterns)
protected def copy(connection: SshConnection): SftpRepository = SftpRepository(name, connection, patterns)
}
/** A repository that conforms to sbt launcher's interface */
private[sbt] class FakeRepository(resolver: DependencyResolver) extends xsbti.Repository {
def rawRepository = new RawRepository(resolver)
}
import Resolver._

View File

@ -69,7 +69,11 @@ object Compiler {
}
compilers(instance, cpOptions, CheaterJavaTool(javac2, javac))
}
@deprecated("Use `compilers(ScalaInstance, ClasspathOptions, Option[File], CompilerBridgeProvider)`.", "0.13.12")
def compilers(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File], ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID)(implicit app: AppConfiguration, log: Logger): Compilers =
compilers(instance, cpOptions, javaHome, sbt.compiler.IvyBridgeProvider(ivyConfiguration, sourcesModule))(app, log)
def compilers(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File], compilerBridgeProvider: CompilerBridgeProvider)(implicit app: AppConfiguration, log: Logger): Compilers =
{
val javac =
AggressiveCompile.directOrFork(instance, cpOptions, javaHome)
@ -81,7 +85,7 @@ object Compiler {
javac.compile(contract, sources, classpath, outputDirectory, options)(log)
def onArgs(f: Seq[String] => Unit): JavaTool = CheaterJavaTool(newJavac, delegate.onArgs(f))
}
val scalac = scalaCompiler(instance, cpOptions, ivyConfiguration, sourcesModule)
val scalac = scalaCompiler(instance, cpOptions, compilerBridgeProvider)
new Compilers(scalac, CheaterJavaTool(javac2, javac))
}
@deprecated("Deprecated in favor of new sbt.compiler.javac package.", "0.13.8")
@ -96,7 +100,7 @@ object Compiler {
val scalac = scalaCompiler(instance, cpOptions)
new Compilers(scalac, javac)
}
@deprecated("Use `scalaCompiler(ScalaInstance, ClasspathOptions, IvyConfiguration, ModuleID)`.", "0.13.10")
@deprecated("Use `scalaCompiler(ScalaInstance, ClasspathOptions, CompilerBridgeProvider)`.", "0.13.12")
def scalaCompiler(instance: ScalaInstance, cpOptions: ClasspathOptions)(implicit app: AppConfiguration, log: Logger): AnalyzingCompiler =
{
val launcher = app.provider.scalaProvider.launcher
@ -104,11 +108,15 @@ object Compiler {
val provider = ComponentCompiler.interfaceProvider(componentManager)
new AnalyzingCompiler(instance, provider, cpOptions)
}
@deprecated("Use `scalaCompiler(ScalaInstance, ClasspathOptions, CompilerBridgeProvider)`.", "0.13.12")
def scalaCompiler(instance: ScalaInstance, cpOptions: ClasspathOptions, ivyConfiguration: IvyConfiguration, sourcesModule: ModuleID)(implicit app: AppConfiguration, log: Logger): AnalyzingCompiler =
scalaCompiler(instance, cpOptions, sbt.compiler.IvyBridgeProvider(ivyConfiguration, sourcesModule))(app, log)
def scalaCompiler(instance: ScalaInstance, cpOptions: ClasspathOptions, compilerBridgeProvider: CompilerBridgeProvider)(implicit app: AppConfiguration, log: Logger): AnalyzingCompiler =
{
val launcher = app.provider.scalaProvider.launcher
val componentManager = new ComponentManager(launcher.globalLock, app.provider.components, Option(launcher.ivyHome), log)
val provider = ComponentCompiler.interfaceProvider(componentManager, ivyConfiguration, sourcesModule)
val provider = ComponentCompiler.interfaceProvider(componentManager, compilerBridgeProvider)
new AnalyzingCompiler(instance, provider, cpOptions)
}

View File

@ -16,8 +16,8 @@ object ConsoleProject {
val scalaProvider = state.configuration.provider.scalaProvider
ScalaInstance(scalaProvider.version, scalaProvider.launcher)
}
val sourcesModule = extracted.get(Keys.scalaCompilerBridgeSource)
val compiler = Compiler.scalaCompiler(scalaInstance, ClasspathOptions.repl, ivyConf, sourcesModule)(state.configuration, log)
val (_, sourcesModule) = extracted.runTask(Keys.compilerBridgeProvider, state)
val compiler = Compiler.scalaCompiler(scalaInstance, ClasspathOptions.repl, sourcesModule)(state.configuration, log)
val imports = BuildUtil.getImports(unit.unit) ++ BuildUtil.importAll(bindings.map(_._1))
val importString = imports.mkString("", ";\n", ";\n\n")
val initCommands = importString + extra

View File

@ -18,7 +18,7 @@ import CrossVersion.{ binarySbtVersion, binaryScalaVersion, partialVersion }
import complete._
import std.TaskExtra._
import sbt.inc.{ Analysis, FileValueCache, IncOptions, Locate }
import sbt.compiler.{ MixedAnalyzingCompiler, AggressiveCompile }
import sbt.compiler.{ MixedAnalyzingCompiler, AggressiveCompile, IvyBridgeProvider }
import testing.{ Framework, Runner, AnnotatedFingerprint, SubclassFingerprint }
import sys.error
@ -235,7 +235,8 @@ object Defaults extends BuildCommon {
val _ = clean.value
IvyActions.cleanCachedResolutionCache(ivyModule.value, streams.value.log)
},
scalaCompilerBridgeSource := ModuleID(xsbti.ArtifactInfo.SbtOrganization, "compiler-interface", sbtVersion.value, Some("component")).sources()
scalaCompilerBridgeSource := ModuleID(xsbti.ArtifactInfo.SbtOrganization, "compiler-interface", sbtVersion.value, Some("component")).sources(),
compilerBridgeProvider := IvyBridgeProvider(bootIvyConfiguration.value, scalaCompilerBridgeSource.value)
)
// must be a val: duplication detected by object identity
private[this] lazy val compileBaseGlobal: Seq[Setting[_]] = globalDefaults(Seq(
@ -265,7 +266,7 @@ object Defaults extends BuildCommon {
}
def compilersSetting = compilers := Compiler.compilers(scalaInstance.value, classpathOptions.value, javaHome.value,
bootIvyConfiguration.value, scalaCompilerBridgeSource.value)(appConfiguration.value, streams.value.log)
compilerBridgeProvider.value)(appConfiguration.value, streams.value.log)
lazy val configTasks = docTaskSettings(doc) ++ inTask(compile)(compileInputsSettings) ++ configGlobal ++ compileAnalysisSettings ++ Seq(
compile <<= compileTask,
@ -1883,6 +1884,7 @@ object Classpaths {
{
import xsbti.Predefined
repo match {
case f: FakeRepository => f.rawRepository
case m: xsbti.MavenRepository => MavenRepository(m.id, m.url.toString)
case i: xsbti.IvyRepository =>
val patterns = Patterns(i.ivyPattern :: Nil, i.artifactPattern :: Nil, mavenCompatible(i), descriptorOptional(i), skipConsistencyCheck(i))

View File

@ -21,6 +21,8 @@ import Configurations.CompilerPlugin
import Types.Id
import KeyRanks._
import sbt.compiler.CompilerBridgeProvider
object Keys {
val TraceValues = "-1 to disable, 0 for up to the first sbt frame, or a positive number to set the maximum number of frames shown."
@ -138,6 +140,7 @@ object Keys {
val printWarnings = TaskKey[Unit]("print-warnings", "Shows warnings from compilation, including ones that weren't printed initially.", BPlusTask)
val fileInputOptions = SettingKey[Seq[String]]("file-input-options", "Options that take file input, which may invalidate the cache.", CSetting)
val scalaCompilerBridgeSource = SettingKey[ModuleID]("scala-compiler-bridge-source", "Configures the module ID of the sources of the compiler bridge.", CSetting)
val compilerBridgeProvider = TaskKey[CompilerBridgeProvider]("compiler-bridge-provider", "Configures how sbt will retrieve the compiler bridge.", CTask)
val clean = TaskKey[Unit]("clean", "Deletes files produced by the build, such as generated sources, compiled classes, and task caches.", APlusTask)
val console = TaskKey[Unit]("console", "Starts the Scala interpreter with the project classes on the classpath.", APlusTask)

View File

@ -7,3 +7,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-javaversioncheck" % "0.1.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.3.0") // 1.6.0 is out but is a hard upgrade
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.2")
addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.2")

View File

@ -0,0 +1,170 @@
package sbt
import java.net.URLClassLoader
import java.util.Properties
/**
* A Main class for running sbt without sbt launcher.
*/
object Main {
def main(args: Array[String]): Unit = {
val appConfiguration = new StaticAppConfiguration(args)
new xMain().run(appConfiguration)
}
}
private object StaticUtils {
val MAIN = "sbt.Main"
val SCALA_ORG = "org.scala-lang"
val COMPILER = "compiler"
val COMPILER_JAR = "scala-compiler.jar"
val LIBRARY = "library"
val LIBRARY_JAR = "scala-library.jar"
val XSBTI = "xsbti"
val XSBTI_JAR = s"interface-${sbtApplicationID.version}.jar"
val thisJAR: File = new File(getClass.getProtectionDomain().getCodeSource().getLocation().toURI().getPath())
def getProperty(loader: ClassLoader, filename: String, property: String): Option[String] =
for {
stream <- Option(loader.getResourceAsStream(filename))
props = new Properties()
_ = props.load(stream)
o <- Option(props get property)
s = o.asInstanceOf[String]
} yield s
}
private class StaticComponentProvider(bootDirectory: File) extends xsbti.ComponentProvider {
override def addToComponent(componentID: String, components: Array[File]): Boolean = {
components foreach { c =>
IO.copyFile(c, componentLocation(componentID) / c.getName)
}
true
}
override def component(componentID: String): Array[File] =
PathFinder(componentLocation(componentID)).***.get.filter(_.isFile).toArray
override def componentLocation(id: String): File =
bootDirectory / s"static-sbt-${sbtApplicationID.version}" / id
override def defineComponent(componentID: String, components: Array[File]): Unit =
addToComponent(componentID, components)
override def lockFile(): File = null
}
private object sbtApplicationID extends xsbti.ApplicationID {
override val groupID: String = xsbti.ArtifactInfo.SbtOrganization
override val name: String = "sbt"
override def version(): String = StaticUtils.getProperty(getClass.getClassLoader, "xsbt.version.properties", "version") getOrElse "unknown"
override val mainClass: String = StaticUtils.MAIN
override val mainComponents: Array[String] = Array.empty
override val crossVersioned: Boolean = false
override val crossVersionedValue: xsbti.CrossValue = xsbti.CrossValue.Disabled
override val classpathExtra: Array[File] = Array.empty
}
private class WeakGlobalLock extends xsbti.GlobalLock {
override def apply[T](lockFile: File, run: java.util.concurrent.Callable[T]): T = run.call
}
private class StaticLauncher(appProvider: StaticAppProvider, scalaProvider: StaticScalaProvider) extends xsbti.Launcher {
override def getScala(version: String): xsbti.ScalaProvider = getScala(version, "")
override def getScala(version: String, reason: String): xsbti.ScalaProvider = getScala(version, reason, StaticUtils.SCALA_ORG)
override def getScala(version: String, reason: String, scalaOrg: String): xsbti.ScalaProvider = {
val myScalaVersion = scalaProvider.version
if (myScalaVersion == version) scalaProvider
else throw new InvalidComponent(s"This launcher can only provide scala $myScalaVersion, asked for scala $version")
}
override def app(id: xsbti.ApplicationID, version: String): xsbti.AppProvider = appProvider
override def topLoader(): ClassLoader = new URLClassLoader(Array.empty)
override def globalLock(): xsbti.GlobalLock = new WeakGlobalLock
override def bootDirectory(): File = new File(sys props "user.home") / ".sbt" / "boot"
override def ivyRepositories(): Array[xsbti.Repository] = Array.empty
override def appRepositories(): Array[xsbti.Repository] = Array(new FakeRepository(new FakeResolver("fakeresolver", bootDirectory / "fakeresolver-cache", modules)))
override def isOverrideRepositories(): Boolean = false
override def ivyHome(): File = null
override def checksums(): Array[String] = Array.empty
private val modules = Map(
("org.scala-sbt", "sbt", "0.13.12-SNAPSHOT") -> Seq(FakeResolver.FakeArtifact("sbt", "jar", "jar", StaticUtils.thisJAR))
)
}
private class StaticScalaProvider(appProvider: StaticAppProvider) extends xsbti.ScalaProvider {
private def getComponent(componentID: String): File = {
val component = appProvider.components.component(componentID)
assert(component.length == 1, s"""Component $componentID should have 1 file, ${component.length} files found: ${component.mkString(", ")}.""")
component(0)
}
override def launcher: xsbti.Launcher = new StaticLauncher(appProvider, this)
override def app(id: xsbti.ApplicationID): xsbti.AppProvider = appProvider
override def compilerJar(): File = getComponent(StaticUtils.COMPILER)
override def libraryJar(): File = getComponent(StaticUtils.LIBRARY)
override def jars(): Array[File] = Array(compilerJar, libraryJar)
override def loader(): ClassLoader = new URLClassLoader(jars map (_.toURI.toURL))
override def version(): String = StaticUtils.getProperty(loader, "compiler.properties", "version.number") getOrElse "unknown"
}
private class StaticAppProvider(appConfig: StaticAppConfiguration) extends xsbti.AppProvider {
if (components.component(StaticUtils.COMPILER).isEmpty) {
installFromResources(StaticUtils.COMPILER_JAR, StaticUtils.COMPILER)
}
if (components.component(StaticUtils.LIBRARY).isEmpty) {
installFromResources(StaticUtils.LIBRARY_JAR, StaticUtils.LIBRARY)
}
if (components.component(StaticUtils.XSBTI).isEmpty) {
installFromResources(StaticUtils.XSBTI_JAR, StaticUtils.XSBTI)
}
override def components(): xsbti.ComponentProvider = new StaticComponentProvider(scalaProvider.launcher.bootDirectory)
override def entryPoint(): Class[_] = loader.getClass
override def id(): xsbti.ApplicationID = sbtApplicationID
override def loader(): ClassLoader = getClass.getClassLoader
override def mainClass(): Class[xsbti.AppMain] = loader.loadClass(id.mainClass).asInstanceOf[Class[xsbti.AppMain]]
override def mainClasspath(): Array[File] = Array(StaticUtils.thisJAR)
override def newMain(): xsbti.AppMain = new xMain
override def scalaProvider(): xsbti.ScalaProvider = new StaticScalaProvider(this)
/**
* Retrieves `fileName` from the resources and installs it in `componentID`.
* @param filename Name of the file to get from the resources.
* @param componentID ID of the component to create.
*/
private def installFromResources(filename: String, componentID: String): Unit =
IO.withTemporaryDirectory { tmp =>
Option(getClass.getClassLoader.getResourceAsStream(filename)) match {
case Some(stream) =>
val target = tmp / filename
val out = new java.io.FileOutputStream(target)
var read = 0
val content = new Array[Byte](1024)
while ({ read = stream.read(content); read != -1 }) {
out.write(content, 0, read)
}
components.defineComponent(componentID, Array(target))
case None =>
sys.error(s"Couldn't install component $componentID: $filename not found on resource path.")
}
}
}
private class StaticAppConfiguration(override val arguments: Array[String]) extends xsbti.AppConfiguration {
override val baseDirectory: File = new File(sys props "user.dir")
override val provider: xsbti.AppProvider = new StaticAppProvider(this)
}