diff --git a/build.sbt b/build.sbt index b1421b366..afe8437c5 100644 --- a/build.sbt +++ b/build.sbt @@ -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 } ) diff --git a/compile/ivy/src/main/scala/sbt/compiler/CompilerBridgeProvider.scala b/compile/ivy/src/main/scala/sbt/compiler/CompilerBridgeProvider.scala new file mode 100644 index 000000000..3e84d4bc0 --- /dev/null +++ b/compile/ivy/src/main/scala/sbt/compiler/CompilerBridgeProvider.scala @@ -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 \ No newline at end of file diff --git a/compile/ivy/src/main/scala/sbt/compiler/ComponentCompiler.scala b/compile/ivy/src/main/scala/sbt/compiler/ComponentCompiler.scala index fa1d57f2d..3b6f3dfcf 100644 --- a/compile/ivy/src/main/scala/sbt/compiler/ComponentCompiler.scala +++ b/compile/ivy/src/main/scala/sbt/compiler/ComponentCompiler.scala @@ -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. diff --git a/ivy/src/main/scala/sbt/FakeResolver.scala b/ivy/src/main/scala/sbt/FakeResolver.scala new file mode 100644 index 000000000..6ef728bdc --- /dev/null +++ b/ivy/src/main/scala/sbt/FakeResolver.scala @@ -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) + } +} diff --git a/ivy/src/main/scala/sbt/Resolver.scala b/ivy/src/main/scala/sbt/Resolver.scala index c0ac1ce31..cce2da583 100644 --- a/ivy/src/main/scala/sbt/Resolver.scala +++ b/ivy/src/main/scala/sbt/Resolver.scala @@ -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._ diff --git a/main/actions/src/main/scala/sbt/Compiler.scala b/main/actions/src/main/scala/sbt/Compiler.scala index 062b11696..20a4ca6ca 100644 --- a/main/actions/src/main/scala/sbt/Compiler.scala +++ b/main/actions/src/main/scala/sbt/Compiler.scala @@ -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) } diff --git a/main/src/main/scala/sbt/ConsoleProject.scala b/main/src/main/scala/sbt/ConsoleProject.scala index 3d66cf552..f444fcb4b 100644 --- a/main/src/main/scala/sbt/ConsoleProject.scala +++ b/main/src/main/scala/sbt/ConsoleProject.scala @@ -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 diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index ae6e93823..2f88004a2 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -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)) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 566d8ed49..1f92cd219 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -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) diff --git a/project/plugins.sbt b/project/plugins.sbt index 611885f9a..a440cc1ec 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -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") \ No newline at end of file diff --git a/sbt/src/main/scala/Main.scala b/sbt/src/main/scala/Main.scala new file mode 100644 index 000000000..cf45221ef --- /dev/null +++ b/sbt/src/main/scala/Main.scala @@ -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) +} +