Merge pull request #1938 from sbt/wip/launcher-as-module

Removing launcher in favor of re-wrapping sbt/launcher module
This commit is contained in:
Josh Suereth 2015-03-26 08:13:46 -04:00
commit 74a9c047f8
59 changed files with 149 additions and 3424 deletions

124
build.sbt
View File

@ -3,7 +3,6 @@ import Util._
import Dependencies._ import Dependencies._
import Licensed._ import Licensed._
import Scope.ThisScope import Scope.ThisScope
import LaunchProguard.{ proguard, Proguard }
import Scripted._ import Scripted._
import StringUtilities.normalize import StringUtilities.normalize
import Sxr.sxr import Sxr.sxr
@ -22,6 +21,7 @@ def commonSettings: Seq[Setting[_]] = Seq(
componentID := None, componentID := None,
crossPaths := false, crossPaths := false,
resolvers += Resolver.typesafeIvyRepo("releases"), resolvers += Resolver.typesafeIvyRepo("releases"),
resolvers += Resolver.sonatypeRepo("snapshots"),
concurrentRestrictions in Global += Util.testExclusiveRestriction, concurrentRestrictions in Global += Util.testExclusiveRestriction,
testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"), testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
javacOptions in compile ++= Seq("-target", "6", "-source", "6", "-Xlint", "-Xlint:-serial"), javacOptions in compile ++= Seq("-target", "6", "-source", "6", "-Xlint", "-Xlint:-serial"),
@ -46,12 +46,28 @@ lazy val root: Project = (project in file(".")).
settings(minimalSettings ++ rootSettings: _*). settings(minimalSettings ++ rootSettings: _*).
settings( settings(
publish := {}, publish := {},
publishLocal := { publishLocal := {}
val p = (proguard in (proguardedLauncherProj, Proguard)).value
IO.copyFile(p, target.value / p.getName)
}
) )
// This is used to configure an sbt-launcher for this version of sbt.
lazy val bundledLauncherProj =
(project in file("launch")).
settings(minimalSettings:_*).
settings(inConfig(Compile)(Transform.configSettings):_*).
settings(Release.launcherSettings(sbtLaunchJar):_*).
enablePlugins(SbtLauncherPlugin).
settings(
name := "sbt-launch",
moduleName := "sbt-launch",
description := "sbt application launcher",
publishArtifact in packageSrc := false,
autoScalaLibrary := false,
publish := Release.deployLauncher.value,
publishLauncher := Release.deployLauncher.value,
packageBin in Compile := sbtLaunchJar.value
)
// This is used only for command aggregation // This is used only for command aggregation
lazy val allPrecompiled: Project = (project in file("all-precompiled")). lazy val allPrecompiled: Project = (project in file("all-precompiled")).
aggregate(precompiled282, precompiled292, precompiled293). aggregate(precompiled282, precompiled292, precompiled293).
@ -63,55 +79,6 @@ lazy val allPrecompiled: Project = (project in file("all-precompiled")).
/* ** subproject declarations ** */ /* ** subproject declarations ** */
// defines the Java interfaces through which the launcher and the launched application communicate
lazy val launchInterfaceProj = (project in launchPath / "interface").
settings(minimalSettings ++ javaOnlySettings: _*).
settings(
name := "Launcher Interface"
)
// the launcher. Retrieves, loads, and runs applications based on a configuration file.
lazy val launchProj = (project in launchPath).
dependsOn(ioProj % "test->test", interfaceProj % Test, launchInterfaceProj).
settings(testedBaseSettings: _*).
settings(
name := "Launcher",
libraryDependencies += ivy,
compile in Test <<= compile in Test dependsOn (publishLocal in interfaceProj, publishLocal in testSamples, publishLocal in launchInterfaceProj)
).
settings(inConfig(Compile)(Transform.configSettings): _*).
settings(inConfig(Compile)(Transform.transSourceSettings ++ Seq(
Transform.inputSourceDirectory <<= (sourceDirectory in crossProj) / "input_sources",
Transform.sourceProperties := Map("cross.package0" -> "xsbt", "cross.package1" -> "boot")
)): _*)
// the proguarded launcher
// the launcher is published with metadata so that the scripted plugin can pull it in
// being proguarded, it shouldn't ever be on a classpath with other jars, however
lazy val proguardedLauncherProj = (project in file("sbt-launch")).
configs(Proguard).
settings(minimalSettings ++ LaunchProguard.settings ++ LaunchProguard.specific(launchProj) ++
Release.launcherSettings(proguard in Proguard): _*).
settings(
name := "sbt-launch",
moduleName := "sbt-launch",
description := "sbt application launcher",
publishArtifact in packageSrc := false,
autoScalaLibrary := false,
publish <<= Seq(publish, Release.deployLauncher).dependOn,
publishLauncher <<= Release.deployLauncher,
packageBin in Compile <<= proguard in Proguard
)
// used to test the retrieving and loading of an application: sample app is packaged and published to the local repository
lazy val testSamples = (project in launchPath / "test-sample").
dependsOn(interfaceProj, launchInterfaceProj).
settings(baseSettings ++ noPublishSettings: _*).
settings(
name := "Launch Test",
libraryDependencies += scalaCompiler.value
)
// defines Java structures used across Scala versions, such as the API structures and relationships extracted by // defines Java structures used across Scala versions, such as the API structures and relationships extracted by
// the analysis compiler phases and passed back to sbt. The API structures are defined in a simple // the analysis compiler phases and passed back to sbt. The API structures are defined in a simple
// format from which Java sources are generated by the datatype generator Projproject // format from which Java sources are generated by the datatype generator Projproject
@ -188,11 +155,11 @@ lazy val ioProj = (project in utilPath / "io").
// Utilities related to reflection, managing Scala versions, and custom class loaders // Utilities related to reflection, managing Scala versions, and custom class loaders
lazy val classpathProj = (project in utilPath / "classpath"). lazy val classpathProj = (project in utilPath / "classpath").
dependsOn(launchInterfaceProj, interfaceProj, ioProj). dependsOn(interfaceProj, ioProj).
settings(testedBaseSettings: _*). settings(testedBaseSettings: _*).
settings( settings(
name := "Classpath", name := "Classpath",
libraryDependencies += scalaCompiler.value libraryDependencies ++= Seq(scalaCompiler.value,Dependencies.launcherInterface)
) )
// Command line-related utilities. // Command line-related utilities.
@ -258,20 +225,20 @@ lazy val logicProj = (project in utilPath / "logic").
// Apache Ivy integration // Apache Ivy integration
lazy val ivyProj = (project in file("ivy")). lazy val ivyProj = (project in file("ivy")).
dependsOn(interfaceProj, launchInterfaceProj, crossProj, logProj % "compile;test->test", ioProj % "compile;test->test", launchProj % "test->test", collectionProj). dependsOn(interfaceProj, crossProj, logProj % "compile;test->test", ioProj % "compile;test->test", /*launchProj % "test->test",*/ collectionProj).
settings(baseSettings: _*). settings(baseSettings: _*).
settings( settings(
name := "Ivy", name := "Ivy",
libraryDependencies ++= Seq(ivy, jsch, sbtSerialization), libraryDependencies ++= Seq(ivy, jsch, sbtSerialization, launcherInterface),
testExclusive) testExclusive)
// Runner for uniform test interface // Runner for uniform test interface
lazy val testingProj = (project in file("testing")). lazy val testingProj = (project in file("testing")).
dependsOn(ioProj, classpathProj, logProj, launchInterfaceProj, testAgentProj). dependsOn(ioProj, classpathProj, logProj, testAgentProj).
settings(baseSettings: _*). settings(baseSettings: _*).
settings( settings(
name := "Testing", name := "Testing",
libraryDependencies += testInterface libraryDependencies ++= Seq(testInterface,launcherInterface)
) )
// Testing agent for running tests in a separate process. // Testing agent for running tests in a separate process.
@ -327,7 +294,7 @@ lazy val runProj = (project in file("run")).
// Compiler-side interface to compiler that is compiled against the compiler being used either in advance or on the fly. // Compiler-side interface to compiler that is compiled against the compiler being used either in advance or on the fly.
// Includes API and Analyzer phases that extract source API and relationships. // Includes API and Analyzer phases that extract source API and relationships.
lazy val compileInterfaceProj = (project in compilePath / "interface"). lazy val compileInterfaceProj = (project in compilePath / "interface").
dependsOn(interfaceProj % "compile;test->test", ioProj % "test->test", logProj % "test->test", launchProj % "test->test", apiProj % "test->test"). dependsOn(interfaceProj % "compile;test->test", ioProj % "test->test", logProj % "test->test", /*launchProj % "test->test",*/ apiProj % "test->test").
settings(baseSettings ++ precompiledSettings: _*). settings(baseSettings ++ precompiledSettings: _*).
settings( settings(
name := "Compiler Interface", name := "Compiler Interface",
@ -366,12 +333,12 @@ lazy val compilePersistProj = (project in compilePath / "persist").
// sbt-side interface to compiler. Calls compiler-side interface reflectively // sbt-side interface to compiler. Calls compiler-side interface reflectively
lazy val compilerProj = (project in compilePath). lazy val compilerProj = (project in compilePath).
dependsOn(launchInterfaceProj, interfaceProj % "compile;test->test", logProj, ioProj, classpathProj, apiProj, classfileProj, dependsOn(interfaceProj % "compile;test->test", logProj, ioProj, classpathProj, apiProj, classfileProj,
logProj % "test->test", launchProj % "test->test"). logProj % "test->test" /*,launchProj % "test->test" */).
settings(testedBaseSettings: _*). settings(testedBaseSettings: _*).
settings( settings(
name := "Compile", name := "Compile",
libraryDependencies += scalaCompiler.value % Test, libraryDependencies ++= Seq(scalaCompiler.value % Test, launcherInterface),
unmanagedJars in Test <<= (packageSrc in compileInterfaceProj in Compile).map(x => Seq(x).classpath) unmanagedJars in Test <<= (packageSrc in compileInterfaceProj in Compile).map(x => Seq(x).classpath)
) )
@ -398,10 +365,11 @@ lazy val scriptedBaseProj = (project in scriptedPath / "base").
) )
lazy val scriptedSbtProj = (project in scriptedPath / "sbt"). lazy val scriptedSbtProj = (project in scriptedPath / "sbt").
dependsOn (ioProj, logProj, processProj, scriptedBaseProj, launchInterfaceProj % "provided"). dependsOn (ioProj, logProj, processProj, scriptedBaseProj).
settings(baseSettings: _*). settings(baseSettings: _*).
settings( settings(
name := "Scripted sbt" name := "Scripted sbt",
libraryDependencies += launcherInterface % "provided"
) )
lazy val scriptedPluginProj = (project in scriptedPath / "plugin"). lazy val scriptedPluginProj = (project in scriptedPath / "plugin").
@ -423,10 +391,11 @@ lazy val actionsProj = (project in mainPath / "actions").
// General command support and core commands not specific to a build system // General command support and core commands not specific to a build system
lazy val commandProj = (project in mainPath / "command"). lazy val commandProj = (project in mainPath / "command").
dependsOn(interfaceProj, ioProj, launchInterfaceProj, logProj, completeProj, classpathProj, crossProj). dependsOn(interfaceProj, ioProj, logProj, completeProj, classpathProj, crossProj).
settings(testedBaseSettings: _*). settings(testedBaseSettings: _*).
settings( settings(
name := "Command" name := "Command",
libraryDependencies += launcherInterface
) )
// Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions // Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions
@ -441,11 +410,11 @@ lazy val mainSettingsProj = (project in mainPath / "settings").
// The main integration project for sbt. It brings all of the Projsystems together, configures them, and provides for overriding conventions. // The main integration project for sbt. It brings all of the Projsystems together, configures them, and provides for overriding conventions.
lazy val mainProj = (project in mainPath). lazy val mainProj = (project in mainPath).
dependsOn (actionsProj, mainSettingsProj, interfaceProj, ioProj, ivyProj, launchInterfaceProj, logProj, logicProj, processProj, runProj, commandProj). dependsOn (actionsProj, mainSettingsProj, interfaceProj, ioProj, ivyProj, logProj, logicProj, processProj, runProj, commandProj).
settings(testedBaseSettings: _*). settings(testedBaseSettings: _*).
settings( settings(
name := "Main", name := "Main",
libraryDependencies ++= scalaXml.value libraryDependencies ++= scalaXml.value ++ Seq(launcherInterface)
) )
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object // Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
@ -471,13 +440,13 @@ lazy val mavenResolverPluginProj = (project in file("sbt-maven-resolver")).
def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask { def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask {
val result = scriptedSource(dir => (s: State) => scriptedParser(dir)).parsed val result = scriptedSource(dir => (s: State) => scriptedParser(dir)).parsed
publishAll.value publishAll.value
doScripted((proguard in Proguard in proguardedLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value, doScripted((sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value,
(scalaInstance in scriptedSbtProj).value, scriptedSource.value, result, scriptedPrescripted.value) (scalaInstance in scriptedSbtProj).value, scriptedSource.value, result, scriptedPrescripted.value)
} }
def scriptedUnpublishedTask: Initialize[InputTask[Unit]] = Def.inputTask { def scriptedUnpublishedTask: Initialize[InputTask[Unit]] = Def.inputTask {
val result = scriptedSource(dir => (s: State) => scriptedParser(dir)).parsed val result = scriptedSource(dir => (s: State) => scriptedParser(dir)).parsed
doScripted((proguard in Proguard in proguardedLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value, doScripted((sbtLaunchJar in bundledLauncherProj).value, (fullClasspath in scriptedSbtProj in Test).value,
(scalaInstance in scriptedSbtProj).value, scriptedSource.value, result, scriptedPrescripted.value) (scalaInstance in scriptedSbtProj).value, scriptedSource.value, result, scriptedPrescripted.value)
} }
@ -486,8 +455,7 @@ lazy val publishLauncher = TaskKey[Unit]("publish-launcher")
lazy val myProvided = config("provided") intransitive lazy val myProvided = config("provided") intransitive
def allProjects = Seq(launchInterfaceProj, launchProj, proguardedLauncherProj, def allProjects = Seq(interfaceProj, apiProj,
testSamples, interfaceProj, apiProj,
controlProj, collectionProj, applyMacroProj, processProj, ioProj, classpathProj, completeProj, controlProj, collectionProj, applyMacroProj, processProj, ioProj, classpathProj, completeProj,
logProj, relationProj, classfileProj, datatypeProj, crossProj, logicProj, ivyProj, logProj, relationProj, classfileProj, datatypeProj, crossProj, logicProj, ivyProj,
testingProj, testAgentProj, taskProj, stdTaskProj, cacheProj, trackingProj, runProj, testingProj, testAgentProj, taskProj, stdTaskProj, cacheProj, trackingProj, runProj,
@ -500,8 +468,8 @@ def projectsWithMyProvided = allProjects.map(p => p.copy(configurations = (p.con
lazy val nonRoots = projectsWithMyProvided.map(p => LocalProject(p.id)) lazy val nonRoots = projectsWithMyProvided.map(p => LocalProject(p.id))
def rootSettings = Release.releaseSettings ++ fullDocSettings ++ def rootSettings = Release.releaseSettings ++ fullDocSettings ++
Util.publishPomSettings ++ otherRootSettings ++ Formatting.sbtFilesSettings ++ Util.publishPomSettings ++ otherRootSettings ++ Formatting.sbtFilesSettings /*++
Transform.conscriptSettings(launchProj) Transform.conscriptSettings(launchProj)*/
def otherRootSettings = Seq( def otherRootSettings = Seq(
Scripted.scriptedPrescripted := { _ => }, Scripted.scriptedPrescripted := { _ => },
Scripted.scripted <<= scriptedTask, Scripted.scripted <<= scriptedTask,
@ -580,7 +548,7 @@ def precompiled(scalav: String): Project = Project(id = normalize("Precompiled "
lazy val safeUnitTests = taskKey[Unit]("Known working tests (for both 2.10 and 2.11)") lazy val safeUnitTests = taskKey[Unit]("Known working tests (for both 2.10 and 2.11)")
lazy val safeProjects: ScopeFilter = ScopeFilter( lazy val safeProjects: ScopeFilter = ScopeFilter(
inProjects(launchProj, mainSettingsProj, mainProj, ivyProj, completeProj, inProjects(mainSettingsProj, mainProj, ivyProj, completeProj,
actionsProj, classpathProj, collectionProj, compileIncrementalProj, actionsProj, classpathProj, collectionProj, compileIncrementalProj,
logProj, runProj, stdTaskProj), logProj, runProj, stdTaskProj),
inConfigurations(Test) inConfigurations(Test)
@ -639,7 +607,7 @@ def customCommands: Seq[Setting[_]] = Seq(
"conscript-configs" :: "conscript-configs" ::
"so compile" :: "so compile" ::
"so publishSigned" :: "so publishSigned" ::
"publishLauncher" :: "bundledLauncherProj/publishLauncher" ::
state state
}, },
// stamp-version doesn't work with ++ or "so". // stamp-version doesn't work with ++ or "so".

View File

@ -336,6 +336,9 @@ class MakePom(val log: Logger) {
val repositories = if (includeAll) allResolvers(settings) else resolvers(settings.getDefaultResolver) val repositories = if (includeAll) allResolvers(settings) else resolvers(settings.getDefaultResolver)
val mavenRepositories = val mavenRepositories =
repositories.flatMap { repositories.flatMap {
// TODO - Would it be ok if bintray were in the pom? We should avoid it for now.
case m: CustomRemoteMavenResolver if m.repo.root == JCenterRepository.root => Nil
case m: IBiblioResolver if m.isM2compatible && m.getRoot == JCenterRepository.root => Nil
case m: CustomRemoteMavenResolver if m.repo.root != DefaultMavenRepository.root => case m: CustomRemoteMavenResolver if m.repo.root != DefaultMavenRepository.root =>
MavenRepository(m.repo.name, m.repo.root) :: Nil MavenRepository(m.repo.name, m.repo.root) :: Nil
case m: IBiblioResolver if m.isM2compatible && m.getRoot != DefaultMavenRepository.root => case m: IBiblioResolver if m.isM2compatible && m.getRoot != DefaultMavenRepository.root =>

View File

@ -1,11 +1,17 @@
package sbt package sbt
import java.io.File import java.io.File
import java.util.concurrent.Callable
import org.specs2._ import org.specs2._
import mutable.Specification import mutable.Specification
import IO.{ createDirectory, delete, touch, withTemporaryDirectory } import IO.{ createDirectory, delete, touch, withTemporaryDirectory }
import org.apache.ivy.util.ChecksumHelper import org.apache.ivy.util.ChecksumHelper
import IfMissing.Fail import IfMissing.Fail
import xsbti.ComponentProvider
// TODO - We need to re-enable this test. Right now, we dont' have a "stub" launcher for this.
// This is testing something which uses a launcher interface, but was grabbing the underlying class directly
// when it really should, instead, be stubbing out the underyling class.
object ComponentManagerTest extends Specification { object ComponentManagerTest extends Specification {
val TestID = "manager-test" val TestID = "manager-test"
@ -82,7 +88,39 @@ object ComponentManagerTest extends Specification {
private def withManagerHome[T](ivyHome: File)(f: ComponentManager => T): T = private def withManagerHome[T](ivyHome: File)(f: ComponentManager => T): T =
TestLogger { logger => TestLogger { logger =>
withTemporaryDirectory { temp => withTemporaryDirectory { temp =>
val mgr = new ComponentManager(xsbt.boot.Locks, new xsbt.boot.ComponentProvider(temp, true), Some(ivyHome), logger) // The actual classes we'll use at runtime.
//val mgr = new ComponentManager(xsbt.boot.Locks, new xsbt.boot.ComponentProvider(temp, true), Some(ivyHome), logger)
// A stub component manager
object provider extends ComponentProvider {
override def componentLocation(id: String): File = new File(temp, id)
override def lockFile(): File = {
IO.createDirectory(temp)
new java.io.File(temp, "sbt.components.lock")
}
override def defineComponent(id: String, files: Array[File]): Unit = {
val location = componentLocation(id)
if (location.exists)
throw new RuntimeException(s"Cannot redefine component. ID: $id, files: ${files.mkString(",")}")
else
IO.copy(files.map { f => f -> new java.io.File(location, f.getName) })
}
override def addToComponent(id: String, files: Array[File]): Boolean = {
val location = componentLocation(id)
IO.copy(files.map { f => f -> new java.io.File(location, f.getName) })
true
}
override def component(id: String): Array[File] =
Option(componentLocation(id).listFiles()).map(_.filter(_.isFile)).getOrElse(Array.empty)
}
// A stubbed locking API.
object locks extends xsbti.GlobalLock {
override def apply[T](lockFile: File, run: Callable[T]): T = {
// TODO - do we need to lock?
run.call()
}
}
val mgr = new ComponentManager(locks, provider, Some(ivyHome), logger)
f(mgr) f(mgr)
} }
} }

View File

@ -1,3 +0,0 @@
Simple Build Tool: Launcher Interface Component
Copyright 2009, 2010 Mark Harrah
Licensed under BSD-style license (see LICENSE)

View File

@ -1,10 +0,0 @@
package xsbti;
import java.io.File;
public interface AppConfiguration
{
public String[] arguments();
public File baseDirectory();
public AppProvider provider();
}

View File

@ -1,25 +0,0 @@
package xsbti;
/**
* The main entry interface for launching applications. Classes which implement this interface
* can be launched via the sbt launcher.
*
* In addition, classes can be adapted into this interface by the launcher if they have a static method
* matching one of these signatures:
*
* - public static void main(String[] args)
* - public static int main(String[] args)
* - public static xsbti.Exit main(String[] args)
*
*/
public interface AppMain
{
/** Run the application and return the result.
*
* @param configuration The configuration used to run the application. Includes arguments and access to launcher features.
* @return
* The result of running this app.
* Note: the result can be things like "Please reboot this application".
*/
public MainResult run(AppConfiguration configuration);
}

View File

@ -1,49 +0,0 @@
package xsbti;
import java.io.File;
/**
* This represents an interface that can generate applications or servers.
*
* This provider grants access to launcher related features associated with
* the id.
*/
public interface AppProvider
{
/** Returns the ScalaProvider that this AppProvider will use. */
public ScalaProvider scalaProvider();
/** The ID of the application that will be created by 'newMain' or 'mainClass'.*/
public ApplicationID id();
/** The classloader used to load this application. */
public ClassLoader loader();
/** Loads the class for the entry point for the application given by 'id'.
* This method will return the same class every invocation.
* That is, the ClassLoader is not recreated each call.
* @deprecated("use entryPoint instead")
*
* Note: This will throw an exception if the launched application does not extend AppMain.
*/
@Deprecated
public Class<? extends AppMain> mainClass();
/** Loads the class for the entry point for the application given by 'id'.
* This method will return the same class every invocation.
* That is, the ClassLoader is not recreated each call.
*/
public Class<?> entryPoint();
/** Creates a new instance of the entry point of the application given by 'id'.
* It is NOT guaranteed that newMain().getClass() == mainClass().
* The sbt launcher can wrap generic static main methods. In this case, there will be a wrapper class,
* and you must use the `entryPoint` method.
* @throws IncompatibleClassChangeError if the configuration used for this Application does not
* represent a launched application.
*/
public AppMain newMain();
/** The classpath from which the main class is loaded, excluding Scala jars.*/
public File[] mainClasspath();
/** Returns a mechanism you can use to install/find/resolve components.
* A component is just a related group of files.
*/
public ComponentProvider components();
}

View File

@ -1,58 +0,0 @@
package xsbti;
import java.io.File;
/**
* This represents an identification for the sbt launcher to load and run
* an sbt launched application using ivy.
*/
public interface ApplicationID
{
/**
* @return
* The Ivy orgnaization / Maven groupId where we can find the application to launch.
*/
public String groupID();
/**
* @return
* The ivy module name / Maven artifactId where we can find the application to launch.
*/
public String name();
/**
* @return
* The ivy/maven version of the module we should resolve.
*/
public String version();
/**
* @return
* The fully qualified name of the class that extends xsbti.AppMain
*/
public String mainClass();
/**
* @return
* Additional ivy components we should resolve with the main application artifacts.
*/
public String[] mainComponents();
/**
* @deprecated
* This method is no longer used if the crossVersionedValue method is available.
*
* @return
* True if the application is cross-versioned by binary-compatible version string,
* False if there is no cross-versioning.
*/
@Deprecated
public boolean crossVersioned();
/**
*
* @since 0.13.0
* @return
* The type of cross-versioning the launcher should use to resolve this artifact.
*/
public CrossValue crossVersionedValue();
/** Files to add to the application classpath. */
public File[] classpathExtra();
}

View File

@ -1,53 +0,0 @@
package xsbti;
import java.io.File;
/**
* A service to locate, install and modify "Components".
*
* A component is essentially a directory and a set of files attached to a unique string id.
*/
public interface ComponentProvider
{
/**
* @param id The component's id string.
* @return
* The "working directory" or base directory for the component. You should perform temporary work here for the component.
*/
public File componentLocation(String id);
/**
* Grab the current component definition.
*
* @param componentID The component's id string.
* @return
* The set of files attached to this component.
*/
public File[] component(String componentID);
/**
* This will define a new component using the files passed in.
*
* Note: The component will copy/move the files into a cache location. You should not use them directly, but
* look them up using the `component` method.
*
* @param componentID The component's id string
* @param components The set of files which defines the component.
*
* @throws BootException if the component is already defined.
*/
public void defineComponent(String componentID, File[] components);
/**
* Modify an existing component by adding files to it.
*
* @param componentID The component's id string
* @param components The set of new files to add to the component.
* @return true if any files were copied and false otherwise.
*
*/
public boolean addToComponent(String componentID, File[] components);
/**
* @return The lockfile you should use to ensure your component cache does not become corrupted.
* May return null if there is no lockfile for this provider.
*/
public File lockFile();
}

View File

@ -1,5 +0,0 @@
package xsbti;
/** A launched application returns an instance of this class in order to communicate to the launcher
* that the application's main thread is finished and the launcher's work is complete, but it should not exit.*/
public interface Continue extends MainResult {}

View File

@ -1,6 +0,0 @@
package xsbti;
public enum CrossValue
{
Disabled, Full, Binary
}

View File

@ -1,10 +0,0 @@
package xsbti;
/**
* A launched application returns an instance of this class in order to communicate to the launcher
* that the application finished and the launcher should exit with the given exit code.
*/
public interface Exit extends MainResult
{
public int code();
}

View File

@ -1,19 +0,0 @@
package xsbti;
public final class FullReload extends RuntimeException
{
private final String[] arguments;
private final boolean clean;
public FullReload(String[] arguments)
{
this.arguments = arguments;
this.clean = false;
}
public FullReload(String[] arguments, boolean clean)
{
this.arguments = arguments;
this.clean = clean;
}
public boolean clean() { return clean; }
public String[] arguments() { return arguments; }
}

View File

@ -1,9 +0,0 @@
package xsbti;
import java.io.File;
import java.util.concurrent.Callable;
public interface GlobalLock
{
public <T> T apply(File lockFile, Callable<T> run);
}

View File

@ -1,14 +0,0 @@
package xsbti;
import java.net.URL;
public interface IvyRepository extends Repository
{
String id();
URL url();
String ivyPattern();
String artifactPattern();
boolean mavenCompatible();
boolean skipConsistencyCheck();
boolean descriptorOptional();
}

View File

@ -1,64 +0,0 @@
package xsbti;
import java.io.File;
public interface Launcher
{
public static final int InterfaceVersion = 1;
public ScalaProvider getScala(String version);
public ScalaProvider getScala(String version, String reason);
public ScalaProvider getScala(String version, String reason, String scalaOrg);
/**
* returns an `AppProvider` which is able to resolve an application
* and instantiate its `xsbti.Main` in a new classloader.
* See [AppProvider] for more details.
* @param id The artifact coordinates of the application.
* @param version The version to resolve
*/
public AppProvider app(ApplicationID id, String version);
/**
* This returns the "top" classloader for a launched application. This classlaoder
* lives somewhere *above* that used for the application. This classloader
* is used for doing any sort of JNA/native library loads so that downstream
* loaders can share native libraries rather than run into "load-once" restrictions.
*/
public ClassLoader topLoader();
/**
* Return the global lock for interacting with the file system.
*
* A mechanism to do file-based locking correctly on the JVM. See
* the [[GlobalLock]] class for more details.
*/
public GlobalLock globalLock();
/** Value of the `sbt.boot.dir` property, or the default
* boot configuration defined in `boot.directory`.
*/
public File bootDirectory();
/** Configured launcher repositories. These repositories
* are the same ones used to load the launcher.
*/
public xsbti.Repository[] ivyRepositories();
/** These are the repositories configured by this launcher
* which should be used by the application when resolving
* further artifacts.
*/
public xsbti.Repository[] appRepositories();
/** The user has configured the launcher with the only repositories
* it wants to use for this applciation.
*/
public boolean isOverrideRepositories();
/**
* The value of `ivy.ivy-home` of the boot properties file.
* This defaults to the `sbt.ivy.home` property, or `~/.ivy2`.
* Use this setting in an application when using Ivy to resolve
* more artifacts.
*
* @returns a file, or null if not set.
*/
public File ivyHome();
/** An array of the checksums that should be checked when retreiving artifacts.
* Configured via the the `ivy.checksums` section of the boot configuration.
* Defaults to sha1, md5 or the value of the `sbt.checksums` property.
*/
public String[] checksums();
}

View File

@ -1,12 +0,0 @@
package xsbti;
/**
* A launched application should return an instance of this from its 'run' method
* to communicate to the launcher what should be done now that the application
* has completed. This interface should be treated as 'sealed', with Exit and Reboot the only
* direct subtypes.
*
* @see xsbti.Exit
* @see xsbti.Reboot
*/
public interface MainResult {}

View File

@ -1,6 +0,0 @@
package xsbti;
enum Manage
{
Nop, Clean, Refresh
}

View File

@ -1,9 +0,0 @@
package xsbti;
import java.net.URL;
public interface MavenRepository extends Repository
{
String id();
URL url();
}

View File

@ -1,29 +0,0 @@
package xsbti;
public enum Predefined
{
Local("local"),
MavenLocal("maven-local"),
MavenCentral("maven-central"),
ScalaToolsReleases("scala-tools-releases"),
ScalaToolsSnapshots("scala-tools-snapshots"),
SonatypeOSSReleases("sonatype-oss-releases"),
SonatypeOSSSnapshots("sonatype-oss-snapshots");
private final String label;
private Predefined(String label) { this.label = label; }
public String toString() { return label; }
public static Predefined toValue(String s)
{
for(Predefined p : values())
if(s.equals(p.toString()))
return p;
StringBuilder msg = new StringBuilder("Expected one of ");
for(Predefined p : values())
msg.append(p.toString()).append(", ");
msg.append("got '").append(s).append("'.");
throw new RuntimeException(msg.toString());
}
}

View File

@ -1,6 +0,0 @@
package xsbti;
public interface PredefinedRepository extends Repository
{
Predefined id();
}

View File

@ -1,16 +0,0 @@
package xsbti;
import java.io.File;
/**
* A launched application returns an instance of this class in order to communicate to the launcher
* that the application should be restarted. Different versions of the application and Scala can be used.
* The application can be given different arguments as well as a new working directory.
*/
public interface Reboot extends MainResult
{
public String[] arguments();
public File baseDirectory();
public String scalaVersion();
public ApplicationID app();
}

View File

@ -1,3 +0,0 @@
package xsbti;
public interface Repository {}

View File

@ -1,12 +0,0 @@
package xsbti;
public final class RetrieveException extends RuntimeException
{
private final String version;
public RetrieveException(String version, String msg)
{
super(msg);
this.version = version;
}
public String version() { return version; }
}

View File

@ -1,29 +0,0 @@
package xsbti;
import java.io.File;
/** Provides access to the jars and classes for a particular version of Scala.*/
public interface ScalaProvider
{
public Launcher launcher();
/** The version of Scala this instance provides.*/
public String version();
/** A ClassLoader that loads the classes from scala-library.jar and scala-compiler.jar.*/
public ClassLoader loader();
/** Returns the scala-library.jar and scala-compiler.jar for this version of Scala. */
public File[] jars();
/**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */
@Deprecated
public File libraryJar();
/**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */
@Deprecated
public File compilerJar();
/** Creates an application provider that will use 'loader()' as the parent ClassLoader for
* the application given by 'id'. This method will retrieve the application if it has not already
* been retrieved.*/
public AppProvider app(ApplicationID id);
}

View File

@ -1,36 +0,0 @@
package xsbti;
/** A running server.
*
* A class implementing this must:
*
* 1. Expose an HTTP port that clients can connect to, returned via the uri method.
* 2. Accept HTTP HEAD requests against the returned URI. These are used as "ping" messages to ensure
* a server is still alive, when new clients connect.
* 3. Create a new thread to execute its service
* 4. Block the calling thread until the server is shutdown via awaitTermination()
*/
public interface Server {
/**
* @return
* A URI denoting the Port which clients can connect to.
*
* Note: we use a URI so that the server can bind to different IP addresses (even a public one) if desired.
* Note: To verify that a server is "up", the sbt launcher will attempt to connect to
* this URI's address and port with a socket. If the connection is accepted, the server is assumed to
* be working.
*/
public java.net.URI uri();
/**
* This should block the calling thread until the server is shutdown.
*
* @return
* The result that should occur from the server.
* Can be:
* - xsbti.Exit: Shutdown this launch
* - xsbti.Reboot: Restart the server
*
*
*/
public xsbti.MainResult awaitTermination();
}

View File

@ -1,17 +0,0 @@
package xsbti;
/** The main entry point for a launched service. This allows applciations
* to instantiate server instances.
*/
public interface ServerMain {
/**
* This method should launch one or more thread(s) which run the service. After the service has
* been started, it should return the port/URI it is listening for connections on.
*
* @param configuration
* The configuration used to launch this service.
* @return
* A running server.
*/
public Server start(AppConfiguration configuration);
}

View File

@ -12,9 +12,11 @@
[repositories] [repositories]
local local
jcenter: https://jcenter.bintray.com/
${{repositories}} ${{repositories}}
maven-central maven-central
[boot] [boot]
directory: ${sbt.boot.directory-${sbt.global.base-${user.home}/.sbt}/boot/} directory: ${sbt.boot.directory-${sbt.global.base-${user.home}/.sbt}/boot/}

View File

@ -1,58 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import java.io.File
// The entry point to the launcher
object Boot {
def main(args: Array[String]) {
val config = parseArgs(args)
// If we havne't exited, we set up some hooks and launch
System.clearProperty("scala.home") // avoid errors from mixing Scala versions in the same JVM
System.setProperty("jline.shutdownhook", "false") // shutdown hooks cause class loader leaks
System.setProperty("jline.esc.timeout", "0") // starts up a thread otherwise
CheckProxy()
run(config)
}
def parseArgs(args: Array[String]): LauncherArguments = {
@annotation.tailrec
def parse(args: List[String], isLocate: Boolean, remaining: List[String]): LauncherArguments =
args match {
case "--version" :: rest =>
println("sbt launcher version " + Package.getPackage("xsbt.boot").getImplementationVersion)
exit(1)
case "--locate" :: rest => parse(rest, true, remaining)
case next :: rest => parse(rest, isLocate, next :: remaining)
case Nil => new LauncherArguments(remaining.reverse, isLocate)
}
parse(args.toList, false, Nil)
}
// this arrangement is because Scala does not always properly optimize away
// the tail recursion in a catch statement
final def run(args: LauncherArguments): Unit = runImpl(args) match {
case Some(newArgs) => run(newArgs)
case None => ()
}
private def runImpl(args: LauncherArguments): Option[LauncherArguments] =
try
Launch(args) map exit
catch {
case b: BootException => errorAndExit(b.toString)
case r: xsbti.RetrieveException => errorAndExit("Error: " + r.getMessage)
case r: xsbti.FullReload => Some(new LauncherArguments(r.arguments.toList, false))
case e: Throwable =>
e.printStackTrace
errorAndExit(Pre.prefixError(e.toString))
}
private def errorAndExit(msg: String): Nothing =
{
System.out.println(msg)
exit(1)
}
private def exit(code: Int): Nothing =
System.exit(code).asInstanceOf[Nothing]
}

View File

@ -1,122 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import java.io.File
// <boot.directory>
// [<scala-org>.]scala-<scala.version>/ [baseDirectoryName]
// lib/ [ScalaDirectoryName]
// <app.name>-<app.version>/ [appDirectoryName]
//
// see also ProjectProperties for the set of constants that apply to the build.properties file in a project
// The scala organization is used as a prefix in baseDirectoryName when a non-standard organization is used.
private object BootConfiguration {
// these are the Scala module identifiers to resolve/retrieve
val ScalaOrg = "org.scala-lang"
val CompilerModuleName = "scala-compiler"
val LibraryModuleName = "scala-library"
val JUnitName = "junit"
val JAnsiVersion = "1.11"
val SbtOrg = "org.scala-sbt"
/** The Ivy conflict manager to use for updating.*/
val ConflictManagerName = "latest-revision"
/** The name of the local Ivy repository, which is used when compiling sbt from source.*/
val LocalIvyName = "local"
/** The pattern used for the local Ivy repository, which is used when compiling sbt from source.*/
val LocalPattern = "[organisation]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]"
/** The artifact pattern used for the local Ivy repository.*/
def LocalArtifactPattern = LocalPattern
/** The Ivy pattern used for the local Ivy repository.*/
def LocalIvyPattern = LocalPattern
final val FjbgPackage = "ch.epfl.lamp.fjbg."
/** The class name prefix used to hide the Scala classes used by this loader from the application */
final val ScalaPackage = "scala."
/** The class name prefix used to hide the Ivy classes used by this loader from the application*/
final val IvyPackage = "org.apache.ivy."
/**
* The class name prefix used to hide the launcher classes from the application.
* Note that access to xsbti classes are allowed.
*/
final val SbtBootPackage = "xsbt.boot."
/**
* The loader will check that these classes can be loaded and will assume that their presence indicates
* the Scala compiler and library have been downloaded.
*/
val TestLoadScalaClasses = "scala.Option" :: "scala.tools.nsc.Global" :: Nil
val ScalaHomeProperty = "scala.home"
val UpdateLogName = "update.log"
val DefaultChecksums = "sha1" :: "md5" :: Nil
val DefaultIvyConfiguration = "default"
/** The name of the directory within the boot directory to retrieve scala to. */
val ScalaDirectoryName = "lib"
/**
* The Ivy pattern to use for retrieving the scala compiler and library. It is relative to the directory
* containing all jars for the requested version of scala.
*/
val scalaRetrievePattern = ScalaDirectoryName + "/[artifact](-[classifier]).[ext]"
def artifactType(classifier: String) =
classifier match {
case "sources" => "src"
case "javadoc" => "doc"
case _ => "jar"
}
/**
* The Ivy pattern to use for retrieving the application and its dependencies. It is relative to the directory
* containing all jars for the requested version of scala.
*/
def appRetrievePattern(appID: xsbti.ApplicationID) = appDirectoryName(appID, "/") + "(/[component])/[artifact]-[revision](-[classifier]).[ext]"
val ScalaVersionPrefix = "scala-"
/** The name of the directory to retrieve the application and its dependencies to.*/
def appDirectoryName(appID: xsbti.ApplicationID, sep: String) = appID.groupID + sep + appID.name + sep + appID.version
/** The name of the directory in the boot directory to put all jars for the given version of scala in.*/
def baseDirectoryName(scalaOrg: String, scalaVersion: Option[String]) = scalaVersion match {
case None => "other"
case Some(sv) => (if (scalaOrg == ScalaOrg) "" else scalaOrg + ".") + ScalaVersionPrefix + sv
}
def extractScalaVersion(dir: File): Option[String] =
{
val name = dir.getName
if (name.contains(ScalaVersionPrefix))
Some(name.substring(name.lastIndexOf(ScalaVersionPrefix) + ScalaVersionPrefix.length))
else
None
}
}
private final class ProxyProperties(
val envURL: String,
val envUser: String,
val envPassword: String,
val sysHost: String,
val sysPort: String,
val sysUser: String,
val sysPassword: String)
private object ProxyProperties {
val http = apply("http")
val https = apply("https")
val ftp = apply("ftp")
def apply(pre: String) = new ProxyProperties(
pre + "_proxy",
pre + "_proxy_user",
pre + "_proxy_pass",
pre + ".proxyHost",
pre + ".proxyPort",
pre + ".proxyUser",
pre + ".proxyPassword"
)
}

View File

@ -1,21 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import java.lang.ref.{ Reference, SoftReference }
import java.util.HashMap
final class Cache[K, X, V](create: (K, X) => V) {
private[this] val delegate = new HashMap[K, Reference[V]]
def apply(k: K, x: X): V = synchronized { getFromReference(k, x, delegate.get(k)) }
private[this] def getFromReference(k: K, x: X, existingRef: Reference[V]) = if (existingRef eq null) newEntry(k, x) else get(k, x, existingRef.get)
private[this] def get(k: K, x: X, existing: V) = if (existing == null) newEntry(k, x) else existing
private[this] def newEntry(k: K, x: X): V =
{
val v = create(k, x)
Pre.assert(v != null, "Value for key " + k + " was null")
delegate.put(k, new SoftReference(v))
v
}
}

View File

@ -1,39 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbt.boot
import Pre._
import java.net.{ MalformedURLException, URL }
object CheckProxy {
def apply() {
import ProxyProperties._
for (pp <- Seq(http, https, ftp))
setFromEnv(pp)
}
private[this] def setFromEnv(conf: ProxyProperties) {
import conf._
val proxyURL = System.getenv(envURL)
if (isDefined(proxyURL) && !isPropertyDefined(sysHost) && !isPropertyDefined(sysPort)) {
try {
val proxy = new URL(proxyURL)
setProperty(sysHost, proxy.getHost)
val port = proxy.getPort
if (port >= 0)
System.setProperty(sysPort, port.toString)
copyEnv(envUser, sysUser)
copyEnv(envPassword, sysPassword)
} catch {
case e: MalformedURLException =>
System.out.println(s"Warning: could not parse $envURL setting: ${e.toString}")
}
}
}
private def copyEnv(envKey: String, sysKey: String) { setProperty(sysKey, System.getenv(envKey)) }
private def setProperty(key: String, value: String) { if (value != null) System.setProperty(key, value) }
private def isPropertyDefined(k: String) = isDefined(System.getProperty(k))
private def isDefined(s: String) = s != null && isNonEmpty(s)
}

View File

@ -1,161 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import Pre._
import java.io.{ File, FileInputStream, InputStreamReader }
import java.net.{ MalformedURLException, URI, URL }
import java.util.regex.Pattern
import scala.collection.immutable.List
import annotation.tailrec
object ConfigurationStorageState extends Enumeration {
val PropertiesFile = value("properties-file")
val SerializedFile = value("serialized-file")
}
object Configuration {
import ConfigurationStorageState._
final val SysPropPrefix = "-D"
def parse(file: URL, baseDirectory: File) = Using(new InputStreamReader(file.openStream, "utf8"))((new ConfigurationParser).apply)
/**
* Finds the configuration location.
*
* Note: Configuration may be previously serialized by a launcher.
*/
@tailrec def find(args: List[String], baseDirectory: File): (URL, List[String], ConfigurationStorageState.Value) =
args match {
case head :: tail if head.startsWith("@load:") => (directConfiguration(head.substring(6), baseDirectory), tail, SerializedFile)
case head :: tail if head.startsWith("@") => (directConfiguration(head.substring(1), baseDirectory), tail, PropertiesFile)
case head :: tail if head.startsWith(SysPropPrefix) =>
setProperty(head stripPrefix SysPropPrefix)
find(tail, baseDirectory)
case _ =>
val propertyConfigured = System.getProperty("sbt.boot.properties")
val url = if (propertyConfigured == null) configurationOnClasspath else configurationFromFile(propertyConfigured, baseDirectory)
(url, args, PropertiesFile)
}
def setProperty(head: String) {
val keyValue = head.split("=", 2)
if (keyValue.length != 2)
System.err.println("Warning: invalid system property '" + head + "'")
else
System.setProperty(keyValue(0), keyValue(1))
}
def configurationOnClasspath: URL =
{
val paths = resourcePaths(guessSbtVersion)
paths.iterator.map(getClass.getResource).find(neNull) getOrElse
(multiPartError("Could not finder sbt launch configuration. Searched classpath for:", paths))
}
def directConfiguration(path: String, baseDirectory: File): URL =
{
try { new URL(path) }
catch { case _: MalformedURLException => configurationFromFile(path, baseDirectory) }
}
def configurationFromFile(path: String, baseDirectory: File): URL =
{
val pathURI = filePathURI(path)
def resolve(against: URI): Option[URL] =
{
val resolved = against.resolve(pathURI) // variant that accepts String doesn't properly escape (#725)
val exists = try { (new File(resolved)).exists } catch { case _: IllegalArgumentException => false }
if (exists) Some(resolved.toURL) else None
}
val against = resolveAgainst(baseDirectory)
// use Iterators so that resolution occurs lazily, for performance
val resolving = against.iterator.flatMap(e => resolve(e).toList.iterator)
if (!resolving.hasNext) multiPartError("Could not find configuration file '" + path + "'. Searched:", against)
resolving.next()
}
def multiPartError[T](firstLine: String, lines: List[T]) = Pre.error((firstLine :: lines).mkString("\n\t"))
def UnspecifiedVersionPart = "Unspecified"
def DefaultVersionPart = "Default"
def DefaultBuildProperties = "project/build.properties"
def SbtVersionProperty = "sbt.version"
val ConfigurationName = "sbt.boot.properties"
val JarBasePath = "/sbt/"
def userConfigurationPath = "/" + ConfigurationName
def defaultConfigurationPath = JarBasePath + ConfigurationName
val baseResourcePaths: List[String] = userConfigurationPath :: defaultConfigurationPath :: Nil
def resourcePaths(sbtVersion: Option[String]): List[String] =
versionParts(sbtVersion) flatMap { part =>
baseResourcePaths map { base =>
base + part
}
}
def fallbackParts: List[String] = "" :: Nil
def versionParts(version: Option[String]): List[String] =
version match {
case None => UnspecifiedVersionPart :: fallbackParts
case Some(v) => versionParts(v)
}
def versionParts(version: String): List[String] =
{
val pattern = Pattern.compile("""(\d+)(\.\d+)(\.\d+)(-.*)?""")
val m = pattern.matcher(version)
if (m.matches())
subPartsIndices flatMap { is => fullMatchOnly(is.map(m.group)) }
else
noMatchParts
}
def noMatchParts: List[String] = DefaultVersionPart :: fallbackParts
private[this] def fullMatchOnly(groups: List[String]): Option[String] =
if (groups.forall(neNull)) Some(groups.mkString) else None
private[this] def subPartsIndices =
(1 :: 2 :: 3 :: 4 :: Nil) ::
(1 :: 2 :: 3 :: Nil) ::
(1 :: 2 :: Nil) ::
(Nil) ::
Nil
// the location of project/build.properties and the name of the property within that file
// that configures the sbt version is configured in sbt.boot.properties.
// We have to hard code them here in order to use them to determine the location of sbt.boot.properties itself
def guessSbtVersion: Option[String] =
{
val props = Pre.readProperties(new File(DefaultBuildProperties))
Option(props.getProperty(SbtVersionProperty))
}
def resolveAgainst(baseDirectory: File): List[URI] =
directoryURI(baseDirectory) ::
directoryURI(new File(System.getProperty("user.home"))) ::
toDirectory(classLocation(getClass).toURI) ::
Nil
def classLocation(cl: Class[_]): URL =
{
val codeSource = cl.getProtectionDomain.getCodeSource
if (codeSource == null) Pre.error("No class location for " + cl)
else codeSource.getLocation
}
// single-arg constructor doesn't properly escape
def filePathURI(path: String): URI = {
if (path.startsWith("file:")) new URI(path)
else {
val f = new File(path)
new URI(if (f.isAbsolute) "file" else null, path, null)
}
}
def directoryURI(dir: File): URI = directoryURI(dir.toURI)
def directoryURI(uri: URI): URI =
{
assert(uri.isAbsolute)
val str = uri.toASCIIString
val dirStr = if (str.endsWith("/")) str else str + "/"
(new URI(dirStr)).normalize
}
def toDirectory(uri: URI): URI =
try {
val file = new File(uri)
val newFile = if (file.isFile) file.getParentFile else file
directoryURI(newFile)
} catch { case _: Exception => uri }
private[this] def neNull: AnyRef => Boolean = _ ne null
}

View File

@ -1,306 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import Pre._
import ConfigurationParser._
import java.lang.Character.isWhitespace
import java.io.{ BufferedReader, File, FileInputStream, InputStreamReader, Reader, StringReader }
import java.net.{ MalformedURLException, URL }
import java.util.regex.{ Matcher, Pattern }
import Matcher.quoteReplacement
import scala.collection.immutable.List
object ConfigurationParser {
def trim(s: Array[String]) = s.map(_.trim).toList
def ids(value: String) = trim(substituteVariables(value).split(",")).filter(isNonEmpty)
private[this] lazy val VarPattern = Pattern.compile("""\$\{([\w.]+)(\-(.*))?\}""")
def substituteVariables(s: String): String = if (s.indexOf('$') >= 0) substituteVariables0(s) else s
// scala.util.Regex brought in 30kB, so we code it explicitly
def substituteVariables0(s: String): String =
{
val m = VarPattern.matcher(s)
val b = new StringBuffer
while (m.find()) {
val key = m.group(1)
val defined = System.getProperty(key)
val value =
if (defined ne null)
defined
else {
val default = m.group(3)
if (default eq null) m.group() else substituteVariables(default)
}
m.appendReplacement(b, quoteReplacement(value))
}
m.appendTail(b)
b.toString
}
implicit val readIDs = ids _
}
class ConfigurationParser {
def apply(file: File): LaunchConfiguration = Using(newReader(file))(apply)
def apply(s: String): LaunchConfiguration = Using(new StringReader(s))(apply)
def apply(reader: Reader): LaunchConfiguration = Using(new BufferedReader(reader))(apply)
private def apply(in: BufferedReader): LaunchConfiguration =
processSections(processLines(readLine(in, Nil, 0)))
private final def readLine(in: BufferedReader, accum: List[Line], index: Int): List[Line] =
in.readLine match {
case null => accum.reverse
case line => readLine(in, ParseLine(line, index) ::: accum, index + 1)
}
private def newReader(file: File) = new InputStreamReader(new FileInputStream(file), "UTF-8")
def readRepositoriesConfig(file: File): List[Repository.Repository] =
Using(newReader(file))(readRepositoriesConfig)
def readRepositoriesConfig(reader: Reader): List[Repository.Repository] =
Using(new BufferedReader(reader))(readRepositoriesConfig)
def readRepositoriesConfig(s: String): List[Repository.Repository] =
Using(new StringReader(s))(readRepositoriesConfig)
private def readRepositoriesConfig(in: BufferedReader): List[Repository.Repository] =
processRepositoriesConfig(processLines(readLine(in, Nil, 0)))
def processRepositoriesConfig(sections: SectionMap): List[Repository.Repository] =
processSection(sections, "repositories", getRepositories)._1
// section -> configuration instance processing
def processSections(sections: SectionMap): LaunchConfiguration =
{
val ((scalaVersion, scalaClassifiers), m1) = processSection(sections, "scala", getScala)
val ((app, appClassifiers), m2) = processSection(m1, "app", getApplication)
val (defaultRepositories, m3) = processSection(m2, "repositories", getRepositories)
val (boot, m4) = processSection(m3, "boot", getBoot)
val (logging, m5) = processSection(m4, "log", getLogging)
val (properties, m6) = processSection(m5, "app-properties", getAppProperties)
val ((ivyHome, checksums, isOverrideRepos, rConfigFile), m7) = processSection(m6, "ivy", getIvy)
val (serverOptions, m8) = processSection(m7, "server", getServer)
check(m8, "section")
val classifiers = Classifiers(scalaClassifiers, appClassifiers)
val repositories = rConfigFile map readRepositoriesConfig getOrElse defaultRepositories
val ivyOptions = IvyOptions(ivyHome, classifiers, repositories, checksums, isOverrideRepos)
// TODO - Read server properties...
new LaunchConfiguration(scalaVersion, ivyOptions, app, boot, logging, properties, serverOptions)
}
def getScala(m: LabelMap) =
{
val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version")
val (scalaClassifiers, m2) = getClassifiers(m1, "Scala classifiers")
check(m2, "label")
(scalaVersion, scalaClassifiers)
}
def getClassifiers(m: LabelMap, label: String): (Value[List[String]], LabelMap) =
process(m, "classifiers", processClassifiers(label))
def processClassifiers(label: String)(value: Option[String]): Value[List[String]] =
value.map(readValue[List[String]](label)) getOrElse new Explicit(Nil)
def getVersion(m: LabelMap, label: String, defaultName: String): (Value[String], LabelMap) = process(m, "version", processVersion(label, defaultName))
def processVersion(label: String, defaultName: String)(value: Option[String]): Value[String] =
value.map(readValue[String](label)).getOrElse(new Implicit(defaultName, None))
def readValue[T](label: String)(implicit read: String => T): String => Value[T] = value0 =>
{
val value = substituteVariables(value0)
if (isEmpty(value)) Pre.error(label + " cannot be empty (omit declaration to use the default)")
try { parsePropertyValue(label, value)(Value.readImplied[T]) }
catch { case e: BootException => new Explicit(read(value)) }
}
def processSection[T](sections: SectionMap, name: String, f: LabelMap => T) =
process[String, LabelMap, T](sections, name, m => f(m default (x => None)))
def process[K, V, T](sections: ListMap[K, V], name: K, f: V => T): (T, ListMap[K, V]) = (f(sections(name)), sections - name)
def check(map: ListMap[String, _], label: String): Unit = if (map.isEmpty) () else Pre.error(map.keys.mkString("Invalid " + label + "(s): ", ",", ""))
def check[T](label: String, pair: (T, ListMap[String, _])): T = { check(pair._2, label); pair._1 }
def id(map: LabelMap, name: String, default: String): (String, LabelMap) =
(substituteVariables(orElse(getOrNone(map, name), default)), map - name)
def getOrNone[K, V](map: ListMap[K, Option[V]], k: K) = orElse(map.get(k), None)
def ids(map: LabelMap, name: String, default: List[String]) =
{
val result = map(name) map ConfigurationParser.ids
(orElse(result, default), map - name)
}
def bool(map: LabelMap, name: String, default: Boolean): (Boolean, LabelMap) =
{
val (b, m) = id(map, name, default.toString)
(toBoolean(b), m)
}
def toFiles(paths: List[String]): List[File] = paths.map(toFile)
def toFile(path: String): File = new File(substituteVariables(path).replace('/', File.separatorChar)) // if the path is relative, it will be resolved by Launch later
def file(map: LabelMap, name: String, default: File): (File, LabelMap) =
(orElse(getOrNone(map, name).map(toFile), default), map - name)
def optfile(map: LabelMap, name: String): (Option[File], LabelMap) =
(getOrNone(map, name).map(toFile), map - name)
def getIvy(m: LabelMap): (Option[File], List[String], Boolean, Option[File]) =
{
val (ivyHome, m1) = optfile(m, "ivy-home")
val (checksums, m2) = ids(m1, "checksums", BootConfiguration.DefaultChecksums)
val (overrideRepos, m3) = bool(m2, "override-build-repos", false)
val (repoConfig, m4) = optfile(m3, "repository-config")
check(m4, "label")
(ivyHome, checksums, overrideRepos, repoConfig filter (_.exists))
}
def getBoot(m: LabelMap): BootSetup =
{
val (dir, m1) = file(m, "directory", toFile("project/boot"))
val (props, m2) = file(m1, "properties", toFile("project/build.properties"))
val (search, m3) = getSearch(m2, props)
val (enableQuick, m4) = bool(m3, "quick-option", false)
val (promptFill, m5) = bool(m4, "prompt-fill", false)
val (promptCreate, m6) = id(m5, "prompt-create", "")
val (lock, m7) = bool(m6, "lock", true)
check(m7, "label")
BootSetup(dir, lock, props, search, promptCreate, enableQuick, promptFill)
}
def getLogging(m: LabelMap): Logging = check("label", process(m, "level", getLevel))
def getLevel(m: Option[String]) = m.map(LogLevel.apply).getOrElse(new Logging(LogLevel.Info))
def getSearch(m: LabelMap, defaultPath: File): (Search, LabelMap) =
ids(m, "search", Nil) match {
case (Nil, newM) => (Search.none, newM)
case (tpe :: Nil, newM) => (Search(tpe, List(defaultPath)), newM)
case (tpe :: paths, newM) => (Search(tpe, toFiles(paths)), newM)
}
def getApplication(m: LabelMap): (Application, Value[List[String]]) =
{
val (org, m1) = id(m, "org", BootConfiguration.SbtOrg)
val (name, m2) = id(m1, "name", "sbt")
val (rev, m3) = getVersion(m2, name + " version", name + ".version")
val (main, m4) = id(m3, "class", "xsbt.Main")
val (components, m5) = ids(m4, "components", List("default"))
val (crossVersioned, m6) = id(m5, "cross-versioned", CrossVersionUtil.binaryString)
val (resources, m7) = ids(m6, "resources", Nil)
val (classifiers, m8) = getClassifiers(m7, "Application classifiers")
check(m8, "label")
val classpathExtra = toArray(toFiles(resources))
val app = new Application(org, name, rev, main, components, LaunchCrossVersion(crossVersioned), classpathExtra)
(app, classifiers)
}
def getServer(m: LabelMap): (Option[ServerConfiguration]) =
{
val (lock, m1) = optfile(m, "lock")
// TODO - JVM args
val (args, m2) = optfile(m1, "jvmargs")
val (props, m3) = optfile(m2, "jvmprops")
lock map { file =>
ServerConfiguration(file, args, props)
}
}
def getRepositories(m: LabelMap): List[Repository.Repository] =
{
import Repository.{ Ivy, Maven, Predefined }
val BootOnly = "bootOnly"
val MvnComp = "mavenCompatible"
val DescriptorOptional = "descriptorOptional"
val DontCheckConsistency = "skipConsistencyCheck"
val OptSet = Set(BootOnly, MvnComp, DescriptorOptional, DontCheckConsistency)
m.toList.map {
case (key, None) => Predefined(key)
case (key, Some(BootOnly)) => Predefined(key, true)
case (key, Some(value)) =>
val r = trim(substituteVariables(value).split(",", 7))
val url = try { new URL(r(0)) } catch { case e: MalformedURLException => Pre.error("Invalid URL specified for '" + key + "': " + e.getMessage) }
val (optionPart, patterns) = r.tail.partition(OptSet.contains(_))
val options = (optionPart.contains(BootOnly), optionPart.contains(MvnComp), optionPart.contains(DescriptorOptional), optionPart.contains(DontCheckConsistency))
(patterns, options) match {
case (both :: Nil, (bo, mc, dso, cc)) => Ivy(key, url, both, both, mavenCompatible = mc, bootOnly = bo, descriptorOptional = dso, skipConsistencyCheck = cc)
case (ivy :: art :: Nil, (bo, mc, dso, cc)) => Ivy(key, url, ivy, art, mavenCompatible = mc, bootOnly = bo, descriptorOptional = dso, skipConsistencyCheck = cc)
case (Nil, (true, false, false, cc)) => Maven(key, url, bootOnly = true)
case (Nil, (false, false, false, false)) => Maven(key, url)
case _ => Pre.error("Could not parse %s: %s".format(key, value))
}
}
}
def getAppProperties(m: LabelMap): List[AppProperty] =
for ((name, Some(value)) <- m.toList) yield {
val map = ListMap(trim(value.split(",")).map(parsePropertyDefinition(name)): _*)
AppProperty(name)(map.get("quick"), map.get("new"), map.get("fill"))
}
def parsePropertyDefinition(name: String)(value: String) = value.split("=", 2) match {
case Array(mode, value) => (mode, parsePropertyValue(name, value)(defineProperty(name)))
case x => Pre.error("Invalid property definition '" + x + "' for property '" + name + "'")
}
def defineProperty(name: String)(action: String, requiredArg: String, optionalArg: Option[String]) =
action match {
case "prompt" => new PromptProperty(requiredArg, optionalArg)
case "set" => new SetProperty(requiredArg)
case _ => Pre.error("Unknown action '" + action + "' for property '" + name + "'")
}
private[this] lazy val propertyPattern = Pattern.compile("""(.+)\((.*)\)(?:\[(.*)\])?""") // examples: prompt(Version)[1.0] or set(1.0)
def parsePropertyValue[T](name: String, definition: String)(f: (String, String, Option[String]) => T): T =
{
val m = propertyPattern.matcher(definition)
if (!m.matches()) Pre.error("Invalid property definition '" + definition + "' for property '" + name + "'")
val optionalArg = m.group(3)
f(m.group(1), m.group(2), if (optionalArg eq null) None else Some(optionalArg))
}
type LabelMap = ListMap[String, Option[String]]
// section-name -> label -> value
type SectionMap = ListMap[String, LabelMap]
def processLines(lines: List[Line]): SectionMap =
{
type State = (SectionMap, Option[String])
val s: State =
(((ListMap.empty.default(x => ListMap.empty[String, Option[String]]), None): State) /: lines) {
case (x, Comment) => x
case ((map, _), s: Section) => (map, Some(s.name))
case ((_, None), l: Labeled) => Pre.error("Label " + l.label + " is not in a section")
case ((map, s @ Some(section)), l: Labeled) =>
val sMap = map(section)
if (sMap.contains(l.label)) Pre.error("Duplicate label '" + l.label + "' in section '" + section + "'")
else (map(section) = (sMap(l.label) = l.value), s)
}
s._1
}
}
sealed trait Line
final class Labeled(val label: String, val value: Option[String]) extends Line
final class Section(val name: String) extends Line
object Comment extends Line
class ParseException(val content: String, val line: Int, val col: Int, val msg: String)
extends BootException("[" + (line + 1) + ", " + (col + 1) + "]" + msg + "\n" + content + "\n" + List.fill(col)(" ").mkString + "^")
object ParseLine {
def apply(content: String, line: Int) =
{
def error(col: Int, msg: String) = throw new ParseException(content, line, col, msg)
def check(condition: Boolean)(col: Int, msg: String) = if (condition) () else error(col, msg)
val trimmed = trimLeading(content)
val offset = content.length - trimmed.length
def section =
{
val closing = trimmed.indexOf(']', 1)
check(closing > 0)(content.length, "Expected ']', found end of line")
val extra = trimmed.substring(closing + 1)
val trimmedExtra = trimLeading(extra)
check(isEmpty(trimmedExtra))(content.length - trimmedExtra.length, "Expected end of line, found '" + extra + "'")
new Section(trimmed.substring(1, closing).trim)
}
def labeled =
{
trimmed.split(":", 2) match {
case Array(label, value) =>
val trimmedValue = value.trim
check(isNonEmpty(trimmedValue))(content.indexOf(':'), "Value for '" + label + "' was empty")
new Labeled(label, Some(trimmedValue))
case x => new Labeled(x.mkString, None)
}
}
if (isEmpty(trimmed)) Nil
else {
val processed =
trimmed.charAt(0) match {
case '#' => Comment
case '[' => section
case _ => labeled
}
processed :: Nil
}
}
}

View File

@ -1,49 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import Pre._
import java.io.{ File, FileInputStream, FileOutputStream }
import java.util.{ Locale, Properties }
import scala.collection.immutable.List
object Initialize {
lazy val selectCreate = (_: AppProperty).create
lazy val selectQuick = (_: AppProperty).quick
lazy val selectFill = (_: AppProperty).fill
def create(file: File, promptCreate: String, enableQuick: Boolean, spec: List[AppProperty]) {
readLine(promptCreate + " (y/N" + (if (enableQuick) "/s" else "") + ") ") match {
case None => declined("")
case Some(line) =>
line.toLowerCase(Locale.ENGLISH) match {
case "y" | "yes" => process(file, spec, selectCreate)
case "s" => process(file, spec, selectQuick)
case "n" | "no" | "" => declined("")
case x =>
System.out.println(" '" + x + "' not understood.")
create(file, promptCreate, enableQuick, spec)
}
}
}
def fill(file: File, spec: List[AppProperty]): Unit = process(file, spec, selectFill)
def process(file: File, appProperties: List[AppProperty], select: AppProperty => Option[PropertyInit]) {
val properties = readProperties(file)
val uninitialized =
for (property <- appProperties; init <- select(property) if properties.getProperty(property.name) == null) yield initialize(properties, property.name, init)
if (uninitialized.nonEmpty) writeProperties(properties, file, "")
}
def initialize(properties: Properties, name: String, init: PropertyInit) {
init match {
case set: SetProperty => properties.setProperty(name, set.value)
case prompt: PromptProperty =>
def noValue = declined("No value provided for " + prompt.label)
readLine(prompt.label + prompt.default.toList.map(" [" + _ + "]").mkString + ": ") match {
case None => noValue
case Some(line) =>
val value = if (isEmpty(line)) orElse(prompt.default, noValue) else line
properties.setProperty(name, value)
}
}
}
}

View File

@ -1,26 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008,2009 David MacIver, Mark Harrah
*/
package xsbt.boot
import Pre._
import scala.collection.immutable.List
class Enumeration extends Serializable {
def elements: List[Value] = members
private lazy val members: List[Value] =
{
val c = getClass
val correspondingFields = ListMap(c.getDeclaredFields.map(f => (f.getName, f)): _*)
c.getMethods.toList flatMap { method =>
if (method.getParameterTypes.length == 0 && classOf[Value].isAssignableFrom(method.getReturnType)) {
for (field <- correspondingFields.get(method.getName) if field.getType == method.getReturnType) yield method.invoke(this).asInstanceOf[Value]
} else
Nil
}
}
def value(s: String) = new Value(s, 0)
def value(s: String, i: Int) = new Value(s, i)
final class Value(override val toString: String, val id: Int) extends Serializable
def toValue(s: String): Value = elements.find(_.toString == s).getOrElse(error("Expected one of " + elements.mkString(",") + " (got: " + s + ")"))
}

View File

@ -1,9 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbt.boot
// The exception to use when an error occurs at the launcher level (and not a nested exception).
// This indicates overrides toString because the exception class name is not needed to understand
// the error message.
class BootException(override val toString: String) extends RuntimeException(toString)

View File

@ -1,39 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbt.boot
import BootConfiguration.{ FjbgPackage, IvyPackage, SbtBootPackage, ScalaPackage }
import scala.collection.immutable.Stream
/**
* A custom class loader to ensure the main part of sbt doesn't load any Scala or
* Ivy classes from the jar containing the loader.
*/
private[boot] final class BootFilteredLoader(parent: ClassLoader) extends ClassLoader(parent) {
@throws(classOf[ClassNotFoundException])
override final def loadClass(className: String, resolve: Boolean): Class[_] =
{
// note that we allow xsbti.*
if (className.startsWith(ScalaPackage) || className.startsWith(IvyPackage) || className.startsWith(SbtBootPackage) || className.startsWith(FjbgPackage))
throw new ClassNotFoundException(className)
else
super.loadClass(className, resolve)
}
override def getResources(name: String) = excludedLoader.getResources(name)
override def getResource(name: String) = excludedLoader.getResource(name)
// the loader to use when a resource is excluded. This needs to be at least parent.getParent so that it skips parent. parent contains
// resources included in the launcher, which need to be ignored. Now that the launcher can be unrooted (not the application entry point),
// this needs to be the Java extension loader (the loader with getParent == null)
private val excludedLoader = Loaders(parent.getParent).head
}
object Loaders {
def apply(loader: ClassLoader): Stream[ClassLoader] =
{
def loaders(loader: ClassLoader, accum: Stream[ClassLoader]): Stream[ClassLoader] =
if (loader eq null) accum else loaders(loader.getParent, Stream.cons(loader, accum))
loaders(loader, Stream.empty)
}
}

View File

@ -1,55 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbt.boot
import Pre._
import java.io.File
import java.net.URI
import scala.collection.immutable.List
object Find { def apply(config: LaunchConfiguration, currentDirectory: File) = (new Find(config))(currentDirectory) }
class Find(config: LaunchConfiguration) {
import config.boot.search
def apply(currentDirectory: File) =
{
val current = currentDirectory.getCanonicalFile
assert(current.isDirectory)
lazy val fromRoot = path(current, Nil).filter(hasProject).map(_.getCanonicalFile)
val found: Option[File] =
search.tpe match {
case Search.RootFirst => fromRoot.headOption
case Search.Nearest => fromRoot.lastOption
case Search.Only =>
if (hasProject(current))
Some(current)
else
fromRoot match {
case Nil => Some(current)
case head :: Nil => Some(head)
case xs =>
System.err.println("Search method is 'only' and multiple ancestor directories match:\n\t" + fromRoot.mkString("\n\t"))
System.exit(1)
None
}
case _ => Some(current)
}
val baseDirectory = orElse(found, current)
System.setProperty("user.dir", baseDirectory.getAbsolutePath)
(ResolvePaths(config, baseDirectory), baseDirectory)
}
private def hasProject(f: File) = f.isDirectory && search.paths.forall(p => ResolvePaths(f, p).exists)
private def path(f: File, acc: List[File]): List[File] = if (f eq null) acc else path(f.getParentFile, f :: acc)
}
object ResolvePaths {
def apply(config: LaunchConfiguration, baseDirectory: File): LaunchConfiguration =
config.map(f => apply(baseDirectory, f))
def apply(baseDirectory: File, f: File): File =
if (f.isAbsolute) f
else {
assert(baseDirectory.isDirectory) // if base directory is not a directory, URI.resolve will not work properly
val uri = new URI(null, null, f.getPath, null)
new File(baseDirectory.toURI.resolve(uri))
}
}

View File

@ -1,23 +0,0 @@
package xsbt.boot
import Pre._
object JAnsi {
def uninstall(loader: ClassLoader): Unit = callJAnsi("systemUninstall", loader)
def install(loader: ClassLoader): Unit = callJAnsi("systemInstall", loader)
private[this] def callJAnsi(methodName: String, loader: ClassLoader): Unit = if (isWindows && !isCygwin) callJAnsiMethod(methodName, loader)
private[this] def callJAnsiMethod(methodName: String, loader: ClassLoader): Unit =
try {
val c = Class.forName("org.fusesource.jansi.AnsiConsole", true, loader)
c.getMethod(methodName).invoke(null)
} catch {
case ignore: ClassNotFoundException =>
/* The below code intentionally traps everything. It technically shouldn't trap the
* non-StackOverflowError VirtualMachineErrors and AWTError would be weird, but this is PermGen
* mitigation code that should not render sbt completely unusable if jansi initialization fails.
* [From Mark Harrah, https://github.com/sbt/sbt/pull/633#issuecomment-11957578].
*/
case ex: Throwable => println("Jansi found on class path but initialization failed: " + ex)
}
}

View File

@ -1,398 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010, 2011 Mark Harrah
*/
package xsbt.boot
import Pre._
import BootConfiguration.{ CompilerModuleName, JAnsiVersion, LibraryModuleName }
import java.io.File
import java.net.{ URL, URLClassLoader, URI }
import java.util.concurrent.Callable
import scala.collection.immutable.List
import scala.annotation.tailrec
import ConfigurationStorageState._
class LauncherArguments(val args: List[String], val isLocate: Boolean)
object Launch {
def apply(arguments: LauncherArguments): Option[Int] = apply((new File("")).getAbsoluteFile, arguments)
def apply(currentDirectory: File, arguments: LauncherArguments): Option[Int] = {
val (configLocation, newArgs2, state) = Configuration.find(arguments.args, currentDirectory)
val config = state match {
case SerializedFile => LaunchConfiguration.restore(configLocation)
case PropertiesFile => parseAndInitializeConfig(configLocation, currentDirectory)
}
if (arguments.isLocate) {
if (newArgs2.nonEmpty) {
// TODO - Print the arguments without exploding proguard size.
System.err.println("Warning: --locate option ignores arguments.")
}
locate(currentDirectory, config)
} else {
// First check to see if there are java system properties we need to set. Then launch the application.
updateProperties(config)
launch(run(Launcher(config)))(makeRunConfig(currentDirectory, config, newArgs2))
}
}
/** Locate a server, print where it is, and exit. */
def locate(currentDirectory: File, config: LaunchConfiguration): Option[Int] = {
config.serverConfig match {
case Some(_) =>
val uri = ServerLocator.locate(currentDirectory, config)
System.out.println(uri.toASCIIString)
Some(0)
case None => sys.error(s"${config.app.groupID}-${config.app.main} is not configured as a server.")
}
}
/**
* Some hackery to allow sys.props to be configured via a file. If this launch config has
* a valid file configured, we load the properties and and apply them to this jvm.
*/
def updateProperties(config: LaunchConfiguration): Unit = {
config.serverConfig match {
case Some(config) =>
config.jvmPropsFile match {
case Some(file) if file.exists =>
try setSystemProperties(readProperties(file))
catch {
case e: Exception => throw new RuntimeException(s"Unable to load server properties file: ${file}", e)
}
case _ =>
}
case None =>
}
}
/** Parses the configuration *and* runs the initialization code that will remove variable references. */
def parseAndInitializeConfig(configLocation: URL, currentDirectory: File): LaunchConfiguration =
{
val (parsed, bd) = parseConfiguration(configLocation, currentDirectory)
resolveConfig(parsed)
}
/** Parse configuration and return it and the baseDirectory of the launch. */
def parseConfiguration(configLocation: URL, currentDirectory: File): (LaunchConfiguration, File) =
Find(Configuration.parse(configLocation, currentDirectory), currentDirectory)
/** Setups the Initialize object so we can fill in system properties in the configuration */
def resolveConfig(parsed: LaunchConfiguration): LaunchConfiguration =
{
// Set up initialize.
val propertiesFile = parsed.boot.properties
import parsed.boot.{ enableQuick, promptCreate, promptFill }
if (isNonEmpty(promptCreate) && !propertiesFile.exists)
Initialize.create(propertiesFile, promptCreate, enableQuick, parsed.appProperties)
else if (promptFill)
Initialize.fill(propertiesFile, parsed.appProperties)
parsed.logging.debug("Parsed configuration: " + parsed)
val resolved = ResolveValues(parsed)
resolved.logging.debug("Resolved configuration: " + resolved)
resolved
}
/** Create run configuration we'll use to launch the app. */
def makeRunConfig(currentDirectory: File, config: LaunchConfiguration, arguments: List[String]): RunConfiguration =
new RunConfiguration(config.getScalaVersion, config.app.toID, currentDirectory, arguments)
/** The actual mechanism used to run a launched application. */
def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult =
{
import config._
val appProvider: xsbti.AppProvider = launcher.app(app, orNull(scalaVersion)) // takes ~40 ms when no update is required
val appConfig: xsbti.AppConfiguration = new AppConfiguration(toArray(arguments), workingDirectory, appProvider)
// TODO - Jansi probably should be configurable via some other mechanism...
JAnsi.install(launcher.topLoader)
try {
val main = appProvider.newMain()
try { withContextLoader(appProvider.loader)(main.run(appConfig)) }
catch { case e: xsbti.FullReload => if (e.clean) delete(launcher.bootDirectory); throw e }
} finally {
JAnsi.uninstall(launcher.topLoader)
}
}
final def launch(run: RunConfiguration => xsbti.MainResult)(config: RunConfiguration): Option[Int] =
{
run(config) match {
case e: xsbti.Exit => Some(e.code)
case c: xsbti.Continue => None
case r: xsbti.Reboot => launch(run)(new RunConfiguration(Option(r.scalaVersion), r.app, r.baseDirectory, r.arguments.toList))
case x => throw new BootException("Invalid main result: " + x + (if (x eq null) "" else " (class: " + x.getClass + ")"))
}
}
private[this] def withContextLoader[T](loader: ClassLoader)(eval: => T): T =
{
val oldLoader = Thread.currentThread.getContextClassLoader
Thread.currentThread.setContextClassLoader(loader)
try { eval } finally { Thread.currentThread.setContextClassLoader(oldLoader) }
}
// Cache of classes for lookup later.
val ServerMainClass = classOf[xsbti.ServerMain]
val AppMainClass = classOf[xsbti.AppMain]
}
final class RunConfiguration(val scalaVersion: Option[String], val app: xsbti.ApplicationID, val workingDirectory: File, val arguments: List[String])
import BootConfiguration.{ appDirectoryName, baseDirectoryName, extractScalaVersion, ScalaDirectoryName, TestLoadScalaClasses, ScalaOrg }
class Launch private[xsbt] (val bootDirectory: File, val lockBoot: Boolean, val ivyOptions: IvyOptions) extends xsbti.Launcher {
import ivyOptions.{ checksums => checksumsList, classifiers, repositories }
bootDirectory.mkdirs
private val scalaProviders = new Cache[(String, String), String, xsbti.ScalaProvider]((x, y) => getScalaProvider(x._1, x._2, y))
def getScala(version: String): xsbti.ScalaProvider = getScala(version, "")
def getScala(version: String, reason: String): xsbti.ScalaProvider = getScala(version, reason, ScalaOrg)
def getScala(version: String, reason: String, scalaOrg: String) = scalaProviders((scalaOrg, version), reason)
def app(id: xsbti.ApplicationID, version: String): xsbti.AppProvider = app(id, Option(version))
def app(id: xsbti.ApplicationID, scalaVersion: Option[String]): xsbti.AppProvider =
getAppProvider(id, scalaVersion, false)
val bootLoader = new BootFilteredLoader(getClass.getClassLoader)
val topLoader = if (isWindows && !isCygwin) jansiLoader(bootLoader) else bootLoader
val updateLockFile = if (lockBoot) Some(new File(bootDirectory, "sbt.boot.lock")) else None
def globalLock: xsbti.GlobalLock = Locks
def ivyHome = orNull(ivyOptions.ivyHome)
def ivyRepositories = (repositories: List[xsbti.Repository]).toArray
def appRepositories = ((repositories filterNot (_.bootOnly)): List[xsbti.Repository]).toArray
def isOverrideRepositories: Boolean = ivyOptions.isOverrideRepositories
def checksums = checksumsList.toArray[String]
// JAnsi needs to be shared between Scala and the application so there aren't two competing versions
def jansiLoader(parent: ClassLoader): ClassLoader =
{
val id = AppID("org.fusesource.jansi", "jansi", JAnsiVersion, "", toArray(Nil), xsbti.CrossValue.Disabled, array())
val configuration = makeConfiguration(ScalaOrg, None)
val jansiHome = appDirectory(new File(bootDirectory, baseDirectoryName(ScalaOrg, None)), id)
val module = appModule(id, None, false, "jansi")
def makeLoader(): ClassLoader = {
val urls = toURLs(wrapNull(jansiHome.listFiles(JarFilter)))
val loader = new URLClassLoader(urls, bootLoader)
checkLoader(loader, module, "org.fusesource.jansi.internal.WindowsSupport" :: Nil, loader)
}
val existingLoader =
if (jansiHome.exists)
try Some(makeLoader()) catch { case e: Exception => None }
else
None
existingLoader getOrElse {
update(module, "")
makeLoader()
}
}
def checkLoader[T](loader: ClassLoader, module: ModuleDefinition, testClasses: Seq[String], ifValid: T): T =
{
val missing = getMissing(loader, testClasses)
if (missing.isEmpty)
ifValid
else
module.retrieveCorrupt(missing)
}
private[this] def makeConfiguration(scalaOrg: String, version: Option[String]): UpdateConfiguration =
new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, scalaOrg, version, repositories, checksumsList)
final def getAppProvider(id: xsbti.ApplicationID, explicitScalaVersion: Option[String], forceAppUpdate: Boolean): xsbti.AppProvider =
locked(new Callable[xsbti.AppProvider] { def call = getAppProvider0(id, explicitScalaVersion, forceAppUpdate) })
@tailrec private[this] final def getAppProvider0(id: xsbti.ApplicationID, explicitScalaVersion: Option[String], forceAppUpdate: Boolean): xsbti.AppProvider =
{
val app = appModule(id, explicitScalaVersion, true, "app")
/** Replace the version of an ApplicationID with the given one, if set. */
def resolveId(appVersion: Option[String], id: xsbti.ApplicationID) = appVersion map { v =>
import id._
AppID(groupID(), name(), v, mainClass(), mainComponents(), crossVersionedValue(), classpathExtra())
} getOrElse id
val baseDirs = (resolvedVersion: Option[String]) => (base: File) => appBaseDirs(base, resolveId(resolvedVersion, id))
def retrieve() = {
val (appv, sv) = update(app, "")
val scalaVersion = strictOr(explicitScalaVersion, sv)
new RetrievedModule(true, app, sv, appv, baseDirs(appv)(scalaHome(ScalaOrg, scalaVersion)))
}
val retrievedApp =
if (forceAppUpdate)
retrieve()
else
existing(app, ScalaOrg, explicitScalaVersion, baseDirs(None)) getOrElse retrieve()
val scalaVersion = getOrError(strictOr(explicitScalaVersion, retrievedApp.detectedScalaVersion), "No Scala version specified or detected")
val scalaProvider = getScala(scalaVersion, "(for " + id.name + ")")
val resolvedId = resolveId(retrievedApp.resolvedAppVersion, id)
val (missing, appProvider) = checkedAppProvider(resolvedId, retrievedApp, scalaProvider)
if (missing.isEmpty)
appProvider
else if (retrievedApp.fresh)
app.retrieveCorrupt(missing)
else
getAppProvider0(resolvedId, explicitScalaVersion, true)
}
def scalaHome(scalaOrg: String, scalaVersion: Option[String]): File = new File(bootDirectory, baseDirectoryName(scalaOrg, scalaVersion))
def appHome(id: xsbti.ApplicationID, scalaVersion: Option[String]): File = appDirectory(scalaHome(ScalaOrg, scalaVersion), id)
def checkedAppProvider(id: xsbti.ApplicationID, module: RetrievedModule, scalaProvider: xsbti.ScalaProvider): (Iterable[String], xsbti.AppProvider) =
{
val p = appProvider(id, module, scalaProvider, appHome(id, Some(scalaProvider.version)))
val missing = getMissing(p.loader, id.mainClass :: Nil)
(missing, p)
}
private[this] def locked[T](c: Callable[T]): T = Locks(orNull(updateLockFile), c)
def getScalaProvider(scalaOrg: String, scalaVersion: String, reason: String): xsbti.ScalaProvider =
locked(new Callable[xsbti.ScalaProvider] { def call = getScalaProvider0(scalaOrg, scalaVersion, reason) })
private[this] final def getScalaProvider0(scalaOrg: String, scalaVersion: String, reason: String) =
{
val scalaM = scalaModule(scalaOrg, scalaVersion)
val (scalaHome, lib) = scalaDirs(scalaM, scalaOrg, scalaVersion)
val baseDirs = lib :: Nil
def provider(retrieved: RetrievedModule): xsbti.ScalaProvider = {
val p = scalaProvider(scalaVersion, retrieved, topLoader, lib)
checkLoader(p.loader, retrieved.definition, TestLoadScalaClasses, p)
}
existing(scalaM, scalaOrg, Some(scalaVersion), _ => baseDirs) flatMap { mod =>
try Some(provider(mod))
catch { case e: Exception => None }
} getOrElse {
val (_, scalaVersion) = update(scalaM, reason)
provider(new RetrievedModule(true, scalaM, scalaVersion, baseDirs))
}
}
def existing(module: ModuleDefinition, scalaOrg: String, explicitScalaVersion: Option[String], baseDirs: File => List[File]): Option[RetrievedModule] =
{
val filter = new java.io.FileFilter {
val explicitName = explicitScalaVersion.map(sv => baseDirectoryName(scalaOrg, Some(sv)))
def accept(file: File) = file.isDirectory && explicitName.forall(_ == file.getName)
}
val retrieved = wrapNull(bootDirectory.listFiles(filter)) flatMap { scalaDir =>
val appDir = directory(scalaDir, module.target)
if (appDir.exists)
new RetrievedModule(false, module, extractScalaVersion(scalaDir), baseDirs(scalaDir)) :: Nil
else
Nil
}
retrieved.headOption
}
def directory(scalaDir: File, target: UpdateTarget): File = target match {
case _: UpdateScala => scalaDir
case ua: UpdateApp => appDirectory(scalaDir, ua.id.toID)
}
def appBaseDirs(scalaHome: File, id: xsbti.ApplicationID): List[File] =
{
val appHome = appDirectory(scalaHome, id)
val components = componentProvider(appHome)
appHome :: id.mainComponents.map(components.componentLocation).toList
}
def appDirectory(base: File, id: xsbti.ApplicationID): File =
new File(base, appDirectoryName(id, File.separator))
def scalaDirs(module: ModuleDefinition, scalaOrg: String, scalaVersion: String): (File, File) =
{
val scalaHome = new File(bootDirectory, baseDirectoryName(scalaOrg, Some(scalaVersion)))
val libDirectory = new File(scalaHome, ScalaDirectoryName)
(scalaHome, libDirectory)
}
def appProvider(appID: xsbti.ApplicationID, app: RetrievedModule, scalaProvider0: xsbti.ScalaProvider, appHome: File): xsbti.AppProvider =
new xsbti.AppProvider {
import Launch.{ ServerMainClass, AppMainClass }
val scalaProvider = scalaProvider0
val id = appID
def mainClasspath = app.fullClasspath
lazy val loader = app.createLoader(scalaProvider.loader)
// TODO - For some reason we can't call this from vanilla scala. We get a
// no such method exception UNLESS we're in the same project.
lazy val entryPoint: Class[T] forSome { type T } =
{
val c = Class.forName(id.mainClass, true, loader)
if (classOf[xsbti.AppMain].isAssignableFrom(c)) c
else if (PlainApplication.isPlainApplication(c)) c
else if (ServerApplication.isServerApplication(c)) c
else sys.error(s"${c} is not an instance of xsbti.AppMain, xsbti.ServerMain nor does it have one of these static methods:\n" +
" * void main(String[] args)\n * int main(String[] args)\n * xsbti.Exit main(String[] args)\n")
}
// Deprecated API. Remove when we can.
def mainClass: Class[T] forSome { type T <: xsbti.AppMain } = entryPoint.asSubclass(AppMainClass)
def newMain(): xsbti.AppMain = {
if (ServerApplication.isServerApplication(entryPoint)) ServerApplication(this)
else if (PlainApplication.isPlainApplication(entryPoint)) PlainApplication(entryPoint)
else if (AppMainClass.isAssignableFrom(entryPoint)) mainClass.newInstance
else throw new IncompatibleClassChangeError(s"Main class ${entryPoint.getName} is not an instance of xsbti.AppMain, xsbti.ServerMain nor does it have a valid `main` method.")
}
lazy val components = componentProvider(appHome)
}
def componentProvider(appHome: File) = new ComponentProvider(appHome, lockBoot)
def scalaProvider(scalaVersion: String, module: RetrievedModule, parentLoader: ClassLoader, scalaLibDir: File): xsbti.ScalaProvider = new xsbti.ScalaProvider {
def launcher = Launch.this
def version = scalaVersion
lazy val loader = module.createLoader(parentLoader)
def compilerJar = new File(scalaLibDir, CompilerModuleName + ".jar")
def libraryJar = new File(scalaLibDir, LibraryModuleName + ".jar")
def jars = module.fullClasspath
def app(id: xsbti.ApplicationID) = Launch.this.app(id, Some(scalaVersion))
}
def appModule(id: xsbti.ApplicationID, scalaVersion: Option[String], getClassifiers: Boolean, tpe: String): ModuleDefinition = new ModuleDefinition(
configuration = makeConfiguration(ScalaOrg, scalaVersion),
target = new UpdateApp(Application(id), if (getClassifiers) Value.get(classifiers.app) else Nil, tpe),
failLabel = id.name + " " + id.version,
extraClasspath = id.classpathExtra
)
def scalaModule(org: String, version: String): ModuleDefinition = new ModuleDefinition(
configuration = makeConfiguration(org, Some(version)),
target = new UpdateScala(Value.get(classifiers.forScala)),
failLabel = "Scala " + version,
extraClasspath = array()
)
/** Returns the resolved appVersion (if this was an App), as well as the scalaVersion. */
def update(mm: ModuleDefinition, reason: String): (Option[String], Option[String]) =
{
val result = (new Update(mm.configuration))(mm.target, reason)
if (result.success) result.appVersion -> result.scalaVersion else mm.retrieveFailed
}
}
object Launcher {
def apply(bootDirectory: File, repositories: List[Repository.Repository]): xsbti.Launcher =
apply(bootDirectory, IvyOptions(None, Classifiers(Nil, Nil), repositories, BootConfiguration.DefaultChecksums, false))
def apply(bootDirectory: File, ivyOptions: IvyOptions): xsbti.Launcher =
apply(bootDirectory, ivyOptions, GetLocks.find)
def apply(bootDirectory: File, ivyOptions: IvyOptions, locks: xsbti.GlobalLock): xsbti.Launcher =
new Launch(bootDirectory, true, ivyOptions) {
override def globalLock = locks
}
def apply(explicit: LaunchConfiguration): xsbti.Launcher =
new Launch(explicit.boot.directory, explicit.boot.lock, explicit.ivyConfiguration)
def defaultAppProvider(baseDirectory: File): xsbti.AppProvider = getAppProvider(baseDirectory, Configuration.configurationOnClasspath)
def getAppProvider(baseDirectory: File, configLocation: URL): xsbti.AppProvider =
{
val parsed = ResolvePaths(Configuration.parse(configLocation, baseDirectory), baseDirectory)
Initialize.process(parsed.boot.properties, parsed.appProperties, Initialize.selectQuick)
val config = ResolveValues(parsed)
val launcher = apply(config)
launcher.app(config.app.toID, orNull(config.getScalaVersion))
}
}
class ComponentProvider(baseDirectory: File, lockBoot: Boolean) extends xsbti.ComponentProvider {
def componentLocation(id: String): File = new File(baseDirectory, id)
def component(id: String) = wrapNull(componentLocation(id).listFiles).filter(_.isFile)
def defineComponent(id: String, files: Array[File]) =
{
val location = componentLocation(id)
if (location.exists)
throw new BootException("Cannot redefine component. ID: " + id + ", files: " + files.mkString(","))
else
Copy(files.toList, location)
}
def addToComponent(id: String, files: Array[File]): Boolean =
Copy(files.toList, componentLocation(id))
def lockFile = if (lockBoot) ComponentProvider.lockFile(baseDirectory) else null // null for the Java interface
}
object ComponentProvider {
def lockFile(baseDirectory: File) =
{
baseDirectory.mkdirs()
new File(baseDirectory, "sbt.components.lock")
}
}

View File

@ -1,154 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package xsbt.boot
import Pre._
import java.io.File
import java.net.URL
import scala.collection.immutable.List
//TODO: use copy constructor, check size change
final case class LaunchConfiguration(scalaVersion: Value[String], ivyConfiguration: IvyOptions, app: Application, boot: BootSetup, logging: Logging, appProperties: List[AppProperty], serverConfig: Option[ServerConfiguration]) {
def isServer: Boolean = serverConfig.isDefined
def getScalaVersion = {
val sv = Value.get(scalaVersion)
if (sv == "auto") None else Some(sv)
}
def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(new Explicit(newScalaVersion), ivyConfiguration, app, boot, logging, appProperties, serverConfig)
def withApp(app: Application) = LaunchConfiguration(scalaVersion, ivyConfiguration, app, boot, logging, appProperties, serverConfig)
def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, ivyConfiguration, app.withVersion(new Explicit(newAppVersion)), boot, logging, appProperties, serverConfig)
// TODO: withExplicit
def withVersions(newScalaVersion: String, newAppVersion: String, classifiers0: Classifiers) =
LaunchConfiguration(new Explicit(newScalaVersion), ivyConfiguration.copy(classifiers = classifiers0), app.withVersion(new Explicit(newAppVersion)), boot, logging, appProperties, serverConfig)
def map(f: File => File) = LaunchConfiguration(scalaVersion, ivyConfiguration.map(f), app.map(f), boot.map(f), logging, appProperties, serverConfig.map(_ map f))
}
object LaunchConfiguration {
// Saves a launch configuration into a file. This is only safe if it is loaded by the *same* launcher version.
def save(config: LaunchConfiguration, f: File): Unit = {
val out = new java.io.ObjectOutputStream(new java.io.FileOutputStream(f))
try out.writeObject(config)
finally out.close()
}
// Restores a launch configuration from a file. This is only safe if it is loaded by the *same* launcher version.
def restore(url: URL): LaunchConfiguration = {
val in = new java.io.ObjectInputStream(url.openConnection.getInputStream)
try in.readObject.asInstanceOf[LaunchConfiguration]
finally in.close()
}
}
final case class ServerConfiguration(lockFile: File, jvmArgs: Option[File], jvmPropsFile: Option[File]) {
def map(f: File => File) =
ServerConfiguration(f(lockFile), jvmArgs map f, jvmPropsFile map f)
}
final case class IvyOptions(ivyHome: Option[File], classifiers: Classifiers, repositories: List[Repository.Repository], checksums: List[String], isOverrideRepositories: Boolean) {
def map(f: File => File) = IvyOptions(ivyHome.map(f), classifiers, repositories, checksums, isOverrideRepositories)
}
sealed trait Value[T] extends Serializable
final class Explicit[T](val value: T) extends Value[T] {
override def toString = value.toString
}
final class Implicit[T](val name: String, val default: Option[T]) extends Value[T] {
require(isNonEmpty(name), "Name cannot be empty")
override def toString = name + (default match { case Some(d) => "[" + d + "]"; case None => "" })
}
object Value {
def get[T](v: Value[T]): T = v match { case e: Explicit[T] => e.value; case _ => throw new BootException("Unresolved version: " + v) }
def readImplied[T](s: String, name: String, default: Option[String])(implicit read: String => T): Value[T] =
if (s == "read") new Implicit(name, default map read) else Pre.error("Expected 'read', got '" + s + "'")
}
final case class Classifiers(forScala: Value[List[String]], app: Value[List[String]])
object Classifiers {
def apply(forScala: List[String], app: List[String]): Classifiers = Classifiers(new Explicit(forScala), new Explicit(app))
}
object LaunchCrossVersion {
def apply(s: String): xsbti.CrossValue =
s match {
case x if CrossVersionUtil.isFull(s) => xsbti.CrossValue.Full
case x if CrossVersionUtil.isBinary(s) => xsbti.CrossValue.Binary
case x if CrossVersionUtil.isDisabled(s) => xsbti.CrossValue.Disabled
case x => Pre.error("Unknown value '" + x + "' for property 'cross-versioned'")
}
}
final case class Application(groupID: String, name: String, version: Value[String], main: String, components: List[String], crossVersioned: xsbti.CrossValue, classpathExtra: Array[File]) {
def getVersion = Value.get(version)
def withVersion(newVersion: Value[String]) = Application(groupID, name, newVersion, main, components, crossVersioned, classpathExtra)
def toID = AppID(groupID, name, getVersion, main, toArray(components), crossVersioned, classpathExtra)
def map(f: File => File) = Application(groupID, name, version, main, components, crossVersioned, classpathExtra.map(f))
}
final case class AppID(groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersionedValue: xsbti.CrossValue, classpathExtra: Array[File]) extends xsbti.ApplicationID {
def crossVersioned: Boolean = crossVersionedValue != xsbti.CrossValue.Disabled
}
object Application {
def apply(id: xsbti.ApplicationID): Application =
{
import id._
Application(groupID, name, new Explicit(version), mainClass, mainComponents.toList, safeCrossVersionedValue(id), classpathExtra)
}
private def safeCrossVersionedValue(id: xsbti.ApplicationID): xsbti.CrossValue =
try id.crossVersionedValue
catch {
case _: AbstractMethodError =>
// Before 0.13 this method did not exist on application, so we need to provide a default value
//in the event we're dealing with an older Application.
if (id.crossVersioned) xsbti.CrossValue.Binary
else xsbti.CrossValue.Disabled
}
}
object Repository {
trait Repository extends xsbti.Repository {
def bootOnly: Boolean
}
final case class Maven(id: String, url: URL, bootOnly: Boolean = false) extends xsbti.MavenRepository with Repository
final case class Ivy(id: String, url: URL, ivyPattern: String, artifactPattern: String, mavenCompatible: Boolean, bootOnly: Boolean = false, descriptorOptional: Boolean = false, skipConsistencyCheck: Boolean = false) extends xsbti.IvyRepository with Repository
final case class Predefined(id: xsbti.Predefined, bootOnly: Boolean = false) extends xsbti.PredefinedRepository with Repository
object Predefined {
def apply(s: String): Predefined = new Predefined(xsbti.Predefined.toValue(s), false)
def apply(s: String, bootOnly: Boolean): Predefined = new Predefined(xsbti.Predefined.toValue(s), bootOnly)
}
def isMavenLocal(repo: xsbti.Repository) = repo match { case p: xsbti.PredefinedRepository => p.id == xsbti.Predefined.MavenLocal; case _ => false }
def defaults: List[xsbti.Repository] = xsbti.Predefined.values.map(x => Predefined(x, false)).toList
}
final case class Search(tpe: Search.Value, paths: List[File])
object Search extends Enumeration {
def none = Search(Current, Nil)
val Only = value("only")
val RootFirst = value("root-first")
val Nearest = value("nearest")
val Current = value("none")
def apply(s: String, paths: List[File]): Search = Search(toValue(s), paths)
}
final case class BootSetup(directory: File, lock: Boolean, properties: File, search: Search, promptCreate: String, enableQuick: Boolean, promptFill: Boolean) {
def map(f: File => File) = BootSetup(f(directory), lock, f(properties), search, promptCreate, enableQuick, promptFill)
}
final case class AppProperty(name: String)(val quick: Option[PropertyInit], val create: Option[PropertyInit], val fill: Option[PropertyInit])
sealed trait PropertyInit
final class SetProperty(val value: String) extends PropertyInit
final class PromptProperty(val label: String, val default: Option[String]) extends PropertyInit
final class Logging(level: LogLevel.Value) extends Serializable {
def log(s: => String, at: LogLevel.Value) = if (level.id <= at.id) stream(at).println("[" + at + "] " + s)
def debug(s: => String) = log(s, LogLevel.Debug)
private def stream(at: LogLevel.Value) = if (at == LogLevel.Error) System.err else System.out
}
object LogLevel extends Enumeration {
val Debug = value("debug", 0)
val Info = value("info", 1)
val Warn = value("warn", 2)
val Error = value("error", 3)
def apply(s: String): Logging = new Logging(toValue(s))
}
final class AppConfiguration(val arguments: Array[String], val baseDirectory: File, val provider: xsbti.AppProvider) extends xsbti.AppConfiguration

View File

@ -1,37 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import Pre._
import scala.collection.{ Iterable, Iterator }
import scala.collection.immutable.List
// preserves iteration order
sealed class ListMap[K, V] private (backing: List[(K, V)]) extends Iterable[(K, V)] // use Iterable because Traversable.toStream loops
{
import ListMap.remove
def update(k: K, v: V) = this.+((k, v))
def +(pair: (K, V)) = copy(pair :: remove(backing, pair._1))
def -(k: K) = copy(remove(backing, k))
def get(k: K): Option[V] = backing.find(_._1 == k).map(_._2)
def keys: List[K] = backing.reverse.map(_._1)
def apply(k: K): V = getOrError(get(k), "Key " + k + " not found")
def contains(k: K): Boolean = get(k).isDefined
def iterator = backing.reverse.iterator
override def isEmpty: Boolean = backing.isEmpty
override def toList = backing.reverse
override def toSeq = toList
protected def copy(newBacking: List[(K, V)]): ListMap[K, V] = new ListMap(newBacking)
def default(defaultF: K => V): ListMap[K, V] =
new ListMap[K, V](backing) {
override def apply(k: K) = super.get(k).getOrElse(defaultF(k))
override def copy(newBacking: List[(K, V)]) = super.copy(newBacking).default(defaultF)
}
override def toString = backing.mkString("ListMap(", ",", ")")
}
object ListMap {
def apply[K, V](pairs: (K, V)*) = new ListMap[K, V](pairs.toList.distinct)
def empty[K, V] = new ListMap[K, V](Nil)
private def remove[K, V](backing: List[(K, V)], k: K) = backing.filter(_._1 != k)
}

View File

@ -1,101 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mark Harrah
*/
package xsbt.boot
import java.io.{ File, FileOutputStream, IOException }
import java.nio.channels.FileChannel
import java.util.concurrent.Callable
import scala.collection.immutable.List
import scala.annotation.tailrec
object GetLocks {
/**
* Searches for Locks in parent class loaders before returning Locks from this class loader.
* Normal class loading doesn't work because the launcher class loader hides xsbt classes.
*/
def find: xsbti.GlobalLock =
Loaders(getClass.getClassLoader.getParent).flatMap(tryGet).headOption.getOrElse(Locks)
private[this] def tryGet(loader: ClassLoader): List[xsbti.GlobalLock] =
try { getLocks0(loader) :: Nil } catch { case e: ClassNotFoundException => Nil }
private[this] def getLocks0(loader: ClassLoader) =
Class.forName("xsbt.boot.Locks$", true, loader).getField("MODULE$").get(null).asInstanceOf[xsbti.GlobalLock]
}
// gets a file lock by first getting a JVM-wide lock.
object Locks extends xsbti.GlobalLock {
private[this] val locks = new Cache[File, Unit, GlobalLock]((f, _) => new GlobalLock(f))
def apply[T](file: File, action: Callable[T]): T = if (file eq null) action.call else apply0(file, action)
private[this] def apply0[T](file: File, action: Callable[T]): T =
{
val lock =
synchronized {
file.getParentFile.mkdirs()
file.createNewFile()
locks(file.getCanonicalFile, ())
}
lock.withLock(action)
}
private[this] class GlobalLock(file: File) {
private[this] var fileLocked = false
def withLock[T](run: Callable[T]): T =
synchronized {
if (fileLocked)
run.call
else {
fileLocked = true
try { ignoringDeadlockAvoided(run) }
finally { fileLocked = false }
}
}
// https://github.com/sbt/sbt/issues/650
// This approach means a real deadlock won't be detected
@tailrec private[this] def ignoringDeadlockAvoided[T](run: Callable[T]): T =
{
val result =
try { Some(withFileLock(run)) }
catch {
case i: IOException if isDeadlockAvoided(i) =>
// there should be a timeout to the deadlock avoidance, so this is just a backup
Thread.sleep(200)
None
}
result match { // workaround for no tailrec optimization in the above try/catch
case Some(t) => t
case None => ignoringDeadlockAvoided(run)
}
}
// The actual message is not specified by FileChannel.lock, so this may need to be adjusted for different JVMs
private[this] def isDeadlockAvoided(i: IOException): Boolean =
i.getMessage == "Resource deadlock avoided"
private[this] def withFileLock[T](run: Callable[T]): T =
{
def withChannelRetries(retries: Int)(channel: FileChannel): T =
try { withChannel(channel) }
catch {
case i: InternalLockNPE =>
if (retries > 0) withChannelRetries(retries - 1)(channel) else throw i
}
def withChannel(channel: FileChannel) =
{
val freeLock = try { channel.tryLock } catch { case e: NullPointerException => throw new InternalLockNPE(e) }
if (freeLock eq null) {
System.out.println("Waiting for lock on " + file + " to be available...");
val lock = try { channel.lock } catch { case e: NullPointerException => throw new InternalLockNPE(e) }
try { run.call }
finally { lock.release() }
} else {
try { run.call }
finally { freeLock.release() }
}
}
Using(new FileOutputStream(file).getChannel)(withChannelRetries(5))
}
}
private[this] final class InternalLockNPE(cause: Exception) extends RuntimeException(cause)
}

View File

@ -1,25 +0,0 @@
package xsbt.boot
import Pre._
import java.io.File
import java.net.URLClassLoader
final class ModuleDefinition(val configuration: UpdateConfiguration, val extraClasspath: Array[File], val target: UpdateTarget, val failLabel: String) {
def retrieveFailed: Nothing = fail("")
def retrieveCorrupt(missing: Iterable[String]): Nothing = fail(": missing " + missing.mkString(", "))
private def fail(extra: String) =
throw new xsbti.RetrieveException(versionString, "Could not retrieve " + failLabel + extra)
private def versionString: String = target match { case _: UpdateScala => configuration.getScalaVersion; case a: UpdateApp => Value.get(a.id.version) }
}
final class RetrievedModule(val fresh: Boolean, val definition: ModuleDefinition, val detectedScalaVersion: Option[String], val resolvedAppVersion: Option[String], val baseDirectories: List[File]) {
/** Use this constructor only when the module exists already, or when its version is not dynamic (so its resolved version would be the same) */
def this(fresh: Boolean, definition: ModuleDefinition, detectedScalaVersion: Option[String], baseDirectories: List[File]) =
this(fresh, definition, detectedScalaVersion, None, baseDirectories)
lazy val classpath: Array[File] = getJars(baseDirectories)
lazy val fullClasspath: Array[File] = concat(classpath, definition.extraClasspath)
def createLoader(parentLoader: ClassLoader): ClassLoader =
new URLClassLoader(toURLs(fullClasspath), parentLoader)
}

View File

@ -1,48 +0,0 @@
package xsbt
package boot
/** A wrapper around 'raw' static methods to meet the sbt application interface. */
class PlainApplication private (mainMethod: java.lang.reflect.Method) extends xsbti.AppMain {
override def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
// TODO - Figure out if the main method returns an Int...
val IntClass = classOf[Int]
val ExitClass = classOf[xsbti.Exit]
// It seems we may need to wrap exceptions here...
try mainMethod.getReturnType match {
case ExitClass =>
mainMethod.invoke(null, configuration.arguments).asInstanceOf[xsbti.Exit]
case IntClass =>
PlainApplication.Exit(mainMethod.invoke(null, configuration.arguments).asInstanceOf[Int])
case _ =>
// Here we still invoke, but return 0 if sucessful (no exceptions).
mainMethod.invoke(null, configuration.arguments)
PlainApplication.Exit(0)
} catch {
// This is only thrown if the underlying reflective call throws.
// Let's expose the underlying error.
case e: java.lang.reflect.InvocationTargetException if e.getCause != null =>
throw e.getCause
}
}
}
/** An object that lets us detect compatible "plain" applications and launch them reflectively. */
object PlainApplication {
def isPlainApplication(clazz: Class[_]): Boolean = findMainMethod(clazz).isDefined
def apply(clazz: Class[_]): xsbti.AppMain =
findMainMethod(clazz) match {
case Some(method) => new PlainApplication(method)
case None => sys.error("Class: " + clazz + " does not have a main method!")
}
private def findMainMethod(clazz: Class[_]): Option[java.lang.reflect.Method] =
try {
val method =
clazz.getMethod("main", classOf[Array[String]])
if (java.lang.reflect.Modifier.isStatic(method.getModifiers)) Some(method)
else None
} catch {
case n: NoSuchMethodException => None
}
case class Exit(code: Int) extends xsbti.Exit
}

View File

@ -1,104 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package xsbt.boot
import scala.collection.immutable.List
import java.io.{ File, FileFilter }
import java.net.{ URL, URLClassLoader }
import java.util.Locale
object Pre {
def readLine(prompt: String): Option[String] = {
val c = System.console()
if (c eq null) None else Option(c.readLine(prompt))
}
def trimLeading(line: String) =
{
def newStart(i: Int): Int = if (i >= line.length || !Character.isWhitespace(line.charAt(i))) i else newStart(i + 1)
line.substring(newStart(0))
}
def isEmpty(line: String) = line.length == 0
def isNonEmpty(line: String) = line.length > 0
def assert(condition: Boolean, msg: => String): Unit = if (!condition) throw new AssertionError(msg)
def assert(condition: Boolean): Unit = assert(condition, "Assertion failed")
def require(condition: Boolean, msg: => String): Unit = if (!condition) throw new IllegalArgumentException(msg)
def error(msg: String): Nothing = throw new BootException(prefixError(msg))
def declined(msg: String): Nothing = throw new BootException(msg)
def prefixError(msg: String): String = "Error during sbt execution: " + msg
def toBoolean(s: String) = java.lang.Boolean.parseBoolean(s)
def toArray[T: ClassManifest](list: List[T]) =
{
val arr = new Array[T](list.length)
def copy(i: Int, rem: List[T]): Unit =
if (i < arr.length) {
arr(i) = rem.head
copy(i + 1, rem.tail)
}
copy(0, list)
arr
}
/* These exist in order to avoid bringing in dependencies on RichInt and ArrayBuffer, among others. */
def concat(a: Array[File], b: Array[File]): Array[File] =
{
val n = new Array[File](a.length + b.length)
java.lang.System.arraycopy(a, 0, n, 0, a.length)
java.lang.System.arraycopy(b, 0, n, a.length, b.length)
n
}
def array(files: File*): Array[File] = toArray(files.toList)
/* Saves creating a closure for default if it has already been evaluated*/
def orElse[T](opt: Option[T], default: T) = if (opt.isDefined) opt.get else default
def wrapNull(a: Array[File]): Array[File] = if (a == null) new Array[File](0) else a
def const[B](b: B): Any => B = _ => b
def strictOr[T](a: Option[T], b: Option[T]): Option[T] = a match { case None => b; case _ => a }
def getOrError[T](a: Option[T], msg: String): T = a match { case None => error(msg); case Some(x) => x }
def orNull[T >: Null](t: Option[T]): T = t match { case None => null; case Some(x) => x }
def getJars(directories: List[File]): Array[File] = toArray(directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter))))
object JarFilter extends FileFilter {
def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar")
}
def getMissing(loader: ClassLoader, classes: Iterable[String]): Iterable[String] =
{
def classMissing(c: String) = try { Class.forName(c, false, loader); false } catch { case e: ClassNotFoundException => true }
classes.toList.filter(classMissing)
}
def toURLs(files: Array[File]): Array[URL] = files.map(_.toURI.toURL)
def toFile(url: URL): File =
try { new File(url.toURI) }
catch { case _: java.net.URISyntaxException => new File(url.getPath) }
def delete(f: File) {
if (f.isDirectory) {
val fs = f.listFiles()
if (fs ne null) fs foreach delete
}
if (f.exists) f.delete()
}
final val isWindows: Boolean = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")
final val isCygwin: Boolean = isWindows && java.lang.Boolean.getBoolean("sbt.cygwin")
import java.util.Properties
import java.io.{ FileInputStream, FileOutputStream }
private[boot] def readProperties(propertiesFile: File) =
{
val properties = new Properties
if (propertiesFile.exists)
Using(new FileInputStream(propertiesFile))(properties.load)
properties
}
private[boot] def writeProperties(properties: Properties, file: File, msg: String): Unit = {
file.getParentFile.mkdirs()
Using(new FileOutputStream(file))(out => properties.store(out, msg))
}
private[boot] def setSystemProperties(properties: Properties): Unit = {
val nameItr = properties.stringPropertyNames.iterator
while (nameItr.hasNext) {
val propName = nameItr.next
System.setProperty(propName, properties.getProperty(propName))
}
}
}

View File

@ -1,43 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbt.boot
import Pre._
import java.io.{ File, FileInputStream }
import java.util.Properties
object ResolveValues {
def apply(conf: LaunchConfiguration): LaunchConfiguration = (new ResolveValues(conf))()
private def trim(s: String) = if (s eq null) None else notEmpty(s.trim)
private def notEmpty(s: String) = if (isEmpty(s)) None else Some(s)
}
import ResolveValues.trim
final class ResolveValues(conf: LaunchConfiguration) {
private def propertiesFile = conf.boot.properties
private lazy val properties = readProperties(propertiesFile)
def apply(): LaunchConfiguration =
{
import conf._
val scalaVersion = resolve(conf.scalaVersion)
val appVersion = resolve(app.version)
val classifiers = resolveClassifiers(ivyConfiguration.classifiers)
withVersions(scalaVersion, appVersion, classifiers)
}
def resolveClassifiers(classifiers: Classifiers): Classifiers =
{
import ConfigurationParser.readIDs
// the added "" ensures that the main jars are retrieved
val scalaClassifiers = "" :: resolve(classifiers.forScala)
val appClassifiers = "" :: resolve(classifiers.app)
Classifiers(new Explicit(scalaClassifiers), new Explicit(appClassifiers))
}
def resolve[T](v: Value[T])(implicit read: String => T): T =
v match {
case e: Explicit[t] => e.value
case i: Implicit[t] =>
trim(properties.getProperty(i.name)) map read orElse
i.default getOrElse ("No " + i.name + " specified in " + propertiesFile)
}
}

View File

@ -1,291 +0,0 @@
package xsbt
package boot
import java.io.File
import scala.util.control.NonFatal
import java.net.URI
import java.io.IOException
import Pre._
import scala.annotation.tailrec
/** A wrapper around 'raw' static methods to meet the sbt application interface. */
class ServerApplication private (provider: xsbti.AppProvider) extends xsbti.AppMain {
import ServerApplication._
override def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
val serverMain = provider.entryPoint.asSubclass(ServerMainClass).newInstance
val server = serverMain.start(configuration)
System.out.println(s"${SERVER_SYNCH_TEXT}${server.uri}")
server.awaitTermination()
}
}
/** An object that lets us detect compatible "plain" applications and launch them reflectively. */
object ServerApplication {
val SERVER_SYNCH_TEXT = "[SERVER-URI]"
val ServerMainClass = classOf[xsbti.ServerMain]
// TODO - We should also adapt friendly static methods into servers, perhaps...
// We could even structurally type things that have a uri + awaitTermination method...
def isServerApplication(clazz: Class[_]): Boolean =
ServerMainClass.isAssignableFrom(clazz)
def apply(provider: xsbti.AppProvider): xsbti.AppMain =
new ServerApplication(provider)
}
object ServerLocator {
// TODO - Probably want to drop this to reduce classfile size
private def locked[U](file: File)(f: => U): U = {
Locks(file, new java.util.concurrent.Callable[U] {
def call(): U = f
})
}
// We use the lock file they give us to write the server info. However,
// it seems we cannot both use the server info file for locking *and*
// read from it successfully. Locking seems to blank the file. SO, we create
// another file near the info file to lock.a
def makeLockFile(f: File): File =
new File(f.getParentFile, s"${f.getName}.lock")
// Launch the process and read the port...
def locate(currentDirectory: File, config: LaunchConfiguration): URI =
config.serverConfig match {
case None => sys.error("No server lock file configured. Cannot locate server.")
case Some(sc) => locked(makeLockFile(sc.lockFile)) {
readProperties(sc.lockFile) match {
case Some(uri) if isReachable(uri) => uri
case _ =>
val uri = ServerLauncher.startServer(currentDirectory, config)
writeProperties(sc.lockFile, uri)
uri
}
}
}
private val SERVER_URI_PROPERTY = "server.uri"
def readProperties(f: File): Option[java.net.URI] = {
try {
val props = Pre.readProperties(f)
props.getProperty(SERVER_URI_PROPERTY) match {
case null => None
case uri => Some(new java.net.URI(uri))
}
} catch {
case e: IOException => None
}
}
def writeProperties(f: File, uri: URI): Unit = {
val props = new java.util.Properties
props.setProperty(SERVER_URI_PROPERTY, uri.toASCIIString)
val output = new java.io.FileOutputStream(f)
val df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ")
df.setTimeZone(java.util.TimeZone.getTimeZone("UTC"))
Pre.writeProperties(props, f, s"Server Startup at ${df.format(new java.util.Date)}")
}
def isReachable(uri: java.net.URI): Boolean =
try {
// TODO - For now we assume if we can connect, it means
// that the server is working...
val socket = new java.net.Socket(uri.getHost, uri.getPort)
try socket.isConnected
finally socket.close()
} catch {
case e: IOException => false
}
}
/** A helper class that dumps incoming values into a print stream. */
class StreamDumper(in: java.io.BufferedReader, out: java.io.PrintStream) extends Thread {
// Don't block the application for this thread.
setDaemon(true)
val endTime = new java.util.concurrent.atomic.AtomicLong(Long.MaxValue)
override def run(): Unit = {
def read(): Unit = if (endTime.get > System.currentTimeMillis) in.readLine match {
case null => ()
case line =>
out.println(line)
out.flush()
read()
}
read()
out.close()
}
def close(waitForErrors: Boolean): Unit = {
// closing "in" blocks forever on Windows, so don't do it;
// just wait a couple seconds to read more stuff if there is
// any stuff.
if (waitForErrors) {
endTime.set(System.currentTimeMillis + 5000)
// at this point we'd rather the dumper thread run
// before we check whether to sleep
Thread.`yield`()
// let ourselves read more (thread should exit on earlier of endTime or EOF)
while (isAlive() && (endTime.get > System.currentTimeMillis))
Thread.sleep(50)
} else {
endTime.set(System.currentTimeMillis)
}
}
}
object ServerLauncher {
import ServerApplication.SERVER_SYNCH_TEXT
def startServer(currentDirectory: File, config: LaunchConfiguration): URI = {
val serverConfig = config.serverConfig match {
case Some(c) => c
case None => throw new RuntimeException("Logic Failure: Attempting to start a server that isn't configured to be a server. Please report a bug.")
}
val launchConfig = java.io.File.createTempFile("sbtlaunch", "config")
if (System.getenv("SBT_SERVER_SAVE_TEMPS") eq null)
launchConfig.deleteOnExit()
LaunchConfiguration.save(config, launchConfig)
val jvmArgs: List[String] = serverJvmArgs(currentDirectory, serverConfig)
val cmd: List[String] =
("java" :: jvmArgs) ++
("-jar" :: defaultLauncherLookup.getCanonicalPath :: s"@load:${launchConfig.toURI.toURL.toString}" :: Nil)
launchProcessAndGetUri(cmd, currentDirectory)
}
// Here we try to isolate all the stupidity of dealing with Java processes.
def launchProcessAndGetUri(cmd: List[String], cwd: File): URI = {
// TODO - Handle windows path stupidity in arguments.
val pb = new java.lang.ProcessBuilder()
pb.command(cmd: _*)
pb.directory(cwd)
val process = pb.start()
// First we need to grab all the input streams, and close the ones we don't care about.
process.getOutputStream.close()
val stderr = process.getErrorStream
val stdout = process.getInputStream
// Now we start dumping out errors.
val errorDumper = new StreamDumper(new java.io.BufferedReader(new java.io.InputStreamReader(stderr)), System.err)
errorDumper.start()
// Now we look for the URI synch value, and then make sure we close the output files.
try readUntilSynch(new java.io.BufferedReader(new java.io.InputStreamReader(stdout))) match {
case Some(uri) => uri
case _ =>
// attempt to get rid of the server (helps prevent hanging / stuck locks,
// though this is not reliable)
try process.destroy() catch { case e: Exception => }
// block a second to try to get stuff from stderr
errorDumper.close(waitForErrors = true)
sys.error(s"Failed to start server process in ${pb.directory} command line ${pb.command}")
} finally {
errorDumper.close(waitForErrors = false)
stdout.close()
// Do not close stderr here because on Windows that will block,
// and since the child process has no reason to exit, it may
// block forever. errorDumper.close() instead owns the problem
// of deciding what to do with stderr.
}
}
object ServerUriLine {
def unapply(in: String): Option[URI] =
if (in startsWith SERVER_SYNCH_TEXT) {
Some(new URI(in.substring(SERVER_SYNCH_TEXT.size)))
} else None
}
/** Reads an input steam until it hits the server synch text and server URI. */
def readUntilSynch(in: java.io.BufferedReader): Option[URI] = {
@tailrec
def read(): Option[URI] = in.readLine match {
case null => None
case ServerUriLine(uri) => Some(uri)
case line => read()
}
try read()
finally in.close()
}
/** Reads all the lines in a file. If it doesn't exist, returns an empty list. Forces UTF-8 strings. */
def readLines(f: File): List[String] =
if (!f.exists) Nil else {
val reader = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(f), "UTF-8"))
@tailrec
def read(current: List[String]): List[String] =
reader.readLine match {
case null => current.reverse
case line => read(line :: current)
}
try read(Nil)
finally reader.close()
}
// None = couldn't figure it out
def javaIsAbove(currentDirectory: File, version: Int): Option[Boolean] = try {
val pb = new java.lang.ProcessBuilder()
// hopefully "java -version" is a lot faster than booting the full JVM.
// not sure how else we can do this.
pb.command("java", "-version")
pb.directory(currentDirectory)
val process = pb.start()
try {
process.getOutputStream.close()
process.getInputStream.close()
val stderr = new java.io.LineNumberReader(new java.io.InputStreamReader(process.getErrorStream))
// Looking for the first line which is `java version "1.7.0_60"` or similar
val lineOption = try Option(stderr.readLine()) finally stderr.close()
val pattern = java.util.regex.Pattern.compile("""java version "[0-9]+\.([0-9]+)\..*".*""")
lineOption flatMap { line =>
val matcher = pattern.matcher(line)
if (matcher.matches()) {
try Some(Integer.parseInt(matcher.group(1)) > version) catch { case NonFatal(_) => None }
} else {
System.err.println(s"Failed to parse version from 'java -version' output '$line'")
None
}
}
} finally {
process.destroy()
try { process.waitFor() } catch { case NonFatal(_) => }
}
} catch {
case e: IOException =>
// both process.start and reading the output streams can throw IOException.
// all OS exceptions from process.start are supposed to be IOException.
System.err.println(s"Failed to run 'java -version': ${e.getClass.getName}: ${e.getMessage}")
None
}
def serverJvmArgs(currentDirectory: File, serverConfig: ServerConfiguration): List[String] =
serverJvmArgs(currentDirectory, serverConfig.jvmArgs map readLines getOrElse Nil)
final val memOptPrefixes = List("-Xmx", "-Xms", "-XX:MaxPermSize", "-XX:PermSize", "-XX:ReservedCodeCacheSize", "-XX:MaxMetaspaceSize", "-XX:MetaspaceSize")
final val defaultMinHeapM = 256
final val defaultMaxHeapM = defaultMinHeapM * 4
final val defaultMinPermM = 64
final val defaultMaxPermM = defaultMinPermM * 4
// this is separate just for the test suite
def serverJvmArgs(currentDirectory: File, baseArgs: List[String]): List[String] = {
// ignore blank lines
val trimmed = baseArgs.map(_.trim).filterNot(_.isEmpty)
// If the user config has provided ANY memory options we bail out and do NOT add
// any defaults. This means people can always fix our mistakes, and it avoids
// issues where the JVM refuses to start because of (for example) min size greater
// than max size. We don't want to deal with coordinating our changes with the
// user configuration.
def isMemoryOption(s: String) = memOptPrefixes.exists(s.startsWith(_))
if (trimmed.exists(isMemoryOption(_)))
trimmed
else {
val permOptions = javaIsAbove(currentDirectory, 7) match {
case Some(true) => List(s"-XX:MetaspaceSize=${defaultMinPermM}m", s"-XX:MaxMetaspaceSize=${defaultMaxPermM}m")
case Some(false) => List(s"-XX:PermSize=${defaultMinPermM}m", s"-XX:MaxPermSize=${defaultMaxPermM}m")
case None => Nil // don't know what we're doing, so don't set options
}
s"-Xms${defaultMinHeapM}m" :: s"-Xmx${defaultMaxHeapM}m" :: (permOptions ++ trimmed)
}
}
def defaultLauncherLookup: File =
try {
val classInLauncher = classOf[AppConfiguration]
val fileOpt = for {
domain <- Option(classInLauncher.getProtectionDomain)
source <- Option(domain.getCodeSource)
location = source.getLocation
} yield toFile(location)
fileOpt.getOrElse(throw new RuntimeException("Could not inspect protection domain or code source"))
} catch {
case e: Throwable => throw new RuntimeException("Unable to find sbt-launch.jar.", e)
}
}

View File

@ -1,426 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010, 2011 Mark Harrah
*/
package xsbt.boot
import Pre._
import java.io.{ File, FileWriter, PrintWriter, Writer }
import java.util.concurrent.Callable
import java.util.regex.Pattern
import java.util.Properties
import org.apache.ivy.{ core, plugins, util, Ivy }
import core.LogOptions
import core.cache.{ CacheMetadataOptions, DefaultRepositoryCacheManager, DefaultResolutionCacheManager }
import core.event.EventManager
import core.module.id.{ ArtifactId, ModuleId, ModuleRevisionId }
import core.module.descriptor.{ Configuration => IvyConfiguration, DefaultDependencyArtifactDescriptor, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor }
import core.module.descriptor.{ Artifact => IArtifact, DefaultExcludeRule, DependencyDescriptor, ExcludeRule }
import core.report.ResolveReport
import core.resolve.{ ResolveEngine, ResolveOptions }
import core.retrieve.{ RetrieveEngine, RetrieveOptions }
import core.sort.SortEngine
import core.settings.IvySettings
import plugins.matcher.{ ExactPatternMatcher, PatternMatcher }
import plugins.resolver.{ BasicResolver, ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver }
import util.{ DefaultMessageLogger, filter, Message, MessageLoggerEngine, url }
import filter.Filter
import url.CredentialsStore
import BootConfiguration._
sealed trait UpdateTarget { def tpe: String; def classifiers: List[String] }
final class UpdateScala(val classifiers: List[String]) extends UpdateTarget { def tpe = "scala" }
final class UpdateApp(val id: Application, val classifiers: List[String], val tpe: String) extends UpdateTarget
final class UpdateConfiguration(val bootDirectory: File, val ivyHome: Option[File], val scalaOrg: String,
val scalaVersion: Option[String], val repositories: List[xsbti.Repository], val checksums: List[String]) {
val resolutionCacheBase = new File(bootDirectory, "resolution-cache")
def getScalaVersion = scalaVersion match { case Some(sv) => sv; case None => "" }
}
final class UpdateResult(val success: Boolean, val scalaVersion: Option[String], val appVersion: Option[String]) {
@deprecated("Please use the other constructor providing appVersion.", "0.13.2")
def this(success: Boolean, scalaVersion: Option[String]) = this(success, scalaVersion, None)
}
/** Ensures that the Scala and application jars exist for the given versions or else downloads them.*/
final class Update(config: UpdateConfiguration) {
import config.{ bootDirectory, checksums, getScalaVersion, ivyHome, repositories, resolutionCacheBase, scalaVersion, scalaOrg }
bootDirectory.mkdirs
private def logFile = new File(bootDirectory, UpdateLogName)
private val logWriter = new PrintWriter(new FileWriter(logFile))
private def addCredentials() {
val optionProps =
Option(System.getProperty("sbt.boot.credentials")) orElse
Option(System.getenv("SBT_CREDENTIALS")) map (path =>
Pre.readProperties(new File(path))
)
optionProps match {
case Some(props) => extractCredentials("realm", "host", "user", "password")(props)
case None => ()
}
extractCredentials("sbt.boot.realm", "sbt.boot.host", "sbt.boot.user", "sbt.boot.password")(System.getProperties)
}
private def extractCredentials(keys: (String, String, String, String))(props: Properties) {
val List(realm, host, user, password) = keys.productIterator.map(key => props.getProperty(key.toString)).toList
if (realm != null && host != null && user != null && password != null)
CredentialsStore.INSTANCE.addCredentials(realm, host, user, password)
}
private lazy val settings =
{
addCredentials()
val settings = new IvySettings
ivyHome match { case Some(dir) => settings.setDefaultIvyUserDir(dir); case None => }
addResolvers(settings)
settings.setVariable("ivy.checksums", checksums mkString ",")
settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
settings.setBaseDir(bootDirectory)
setScalaVariable(settings, scalaVersion)
settings
}
private[this] def setScalaVariable(settings: IvySettings, scalaVersion: Option[String]): Unit =
scalaVersion match { case Some(sv) => settings.setVariable("scala", sv); case None => }
private lazy val ivy =
{
val ivy = new Ivy() { private val loggerEngine = new SbtMessageLoggerEngine; override def getLoggerEngine = loggerEngine }
ivy.setSettings(settings)
ivy.bind()
ivy
}
// should be the same file as is used in the Ivy module
private lazy val ivyLockFile = new File(settings.getDefaultIvyUserDir, ".sbt.ivy.lock")
/** The main entry point of this class for use by the Update module. It runs Ivy */
def apply(target: UpdateTarget, reason: String): UpdateResult =
{
Message.setDefaultLogger(new SbtIvyLogger(logWriter))
val action = new Callable[UpdateResult] { def call = lockedApply(target, reason) }
Locks(ivyLockFile, action)
}
private def lockedApply(target: UpdateTarget, reason: String): UpdateResult =
{
ivy.pushContext()
try { update(target, reason) }
catch {
case e: Exception =>
e.printStackTrace(logWriter)
log(e.toString)
System.out.println(" (see " + logFile + " for complete log)")
new UpdateResult(false, None, None)
} finally {
logWriter.close()
ivy.popContext()
delete(resolutionCacheBase)
}
}
/** Runs update for the specified target (updates either the scala or appliciation jars for building the project) */
private def update(target: UpdateTarget, reason: String): UpdateResult =
{
import IvyConfiguration.Visibility.PUBLIC
// the actual module id here is not that important
val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot-" + target.tpe, "1.0"), "release", null, false)
moduleID.setLastModified(System.currentTimeMillis)
moduleID.addConfiguration(new IvyConfiguration(DefaultIvyConfiguration, PUBLIC, "", new Array(0), true, null))
// add dependencies based on which target needs updating
val dep = target match {
case u: UpdateScala =>
val scalaVersion = getScalaVersion
addDependency(moduleID, scalaOrg, CompilerModuleName, scalaVersion, "default;optional(default)", u.classifiers)
val ddesc = addDependency(moduleID, scalaOrg, LibraryModuleName, scalaVersion, "default", u.classifiers)
excludeJUnit(moduleID)
val scalaOrgString = if (scalaOrg != ScalaOrg) " " + scalaOrg else ""
System.out.println("Getting" + scalaOrgString + " Scala " + scalaVersion + " " + reason + "...")
ddesc.getDependencyId
case u: UpdateApp =>
val app = u.id
val resolvedName = (app.crossVersioned, scalaVersion) match {
case (xsbti.CrossValue.Full, Some(sv)) => app.name + "_" + sv
case (xsbti.CrossValue.Binary, Some(sv)) => app.name + "_" + CrossVersionUtil.binaryScalaVersion(sv)
case _ => app.name
}
val ddesc = addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", u.classifiers)
System.out.println("Getting " + app.groupID + " " + resolvedName + " " + app.getVersion + " " + reason + "...")
ddesc.getDependencyId
}
update(moduleID, target, dep)
}
/** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */
private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget, dep: ModuleId): UpdateResult =
{
val eventManager = new EventManager
val (autoScalaVersion, depVersion) = resolve(eventManager, moduleID, dep)
// Fix up target.id with the depVersion that we know for sure is resolved (not dynamic) -- this way, `retrieve`
// will put them in the right version directory.
val target1 = (depVersion, target) match {
case (Some(dv), u: UpdateApp) =>
import u._; new UpdateApp(id.copy(version = new Explicit(dv)), classifiers, tpe)
case _ => target
}
setScalaVariable(settings, autoScalaVersion)
retrieve(eventManager, moduleID, target1, autoScalaVersion)
new UpdateResult(true, autoScalaVersion, depVersion)
}
private def createID(organization: String, name: String, revision: String) =
ModuleRevisionId.newInstance(organization, name, revision)
/** Adds the given dependency to the default configuration of 'moduleID'. */
private def addDependency(moduleID: DefaultModuleDescriptor, organization: String, name: String, revision: String, conf: String, classifiers: List[String]) =
{
val dep = new DefaultDependencyDescriptor(moduleID, createID(organization, name, revision), false, false, true)
for (c <- conf.split(";"))
dep.addDependencyConfiguration(DefaultIvyConfiguration, c)
for (classifier <- classifiers)
addClassifier(dep, name, classifier)
moduleID.addDependency(dep)
dep
}
private def addClassifier(dep: DefaultDependencyDescriptor, name: String, classifier: String) {
val extraMap = new java.util.HashMap[String, String]
if (!isEmpty(classifier))
extraMap.put("e:classifier", classifier)
val ivyArtifact = new DefaultDependencyArtifactDescriptor(dep, name, artifactType(classifier), "jar", null, extraMap)
for (conf <- dep.getModuleConfigurations)
dep.addDependencyArtifact(conf, ivyArtifact)
}
private def excludeJUnit(module: DefaultModuleDescriptor): Unit = module.addExcludeRule(excludeRule(JUnitName, JUnitName))
private def excludeRule(organization: String, name: String): ExcludeRule =
{
val artifact = new ArtifactId(ModuleId.newInstance(organization, name), "*", "*", "*")
val rule = new DefaultExcludeRule(artifact, ExactPatternMatcher.INSTANCE, java.util.Collections.emptyMap[AnyRef, AnyRef])
rule.addConfiguration(DefaultIvyConfiguration)
rule
}
val scalaLibraryId = ModuleId.newInstance(ScalaOrg, LibraryModuleName)
// Returns the version of the scala library, as well as `dep` (a dependency of `module`) after it's been resolved
private def resolve(eventManager: EventManager, module: ModuleDescriptor, dep: ModuleId): (Option[String], Option[String]) =
{
val resolveOptions = new ResolveOptions
// this reduces the substantial logging done by Ivy, including the progress dots when downloading artifacts
resolveOptions.setLog(LogOptions.LOG_DOWNLOAD_ONLY)
resolveOptions.setCheckIfChanged(false)
val resolveEngine = new ResolveEngine(settings, eventManager, new SortEngine(settings))
val resolveReport = resolveEngine.resolve(module, resolveOptions)
if (resolveReport.hasError) {
logExceptions(resolveReport)
val seen = new java.util.LinkedHashSet[Any]
seen.addAll(resolveReport.getAllProblemMessages)
System.out.println(seen.toArray.mkString(System.getProperty("line.separator")))
error("Error retrieving required libraries")
}
val modules = moduleRevisionIDs(resolveReport)
extractVersion(modules, scalaLibraryId) -> extractVersion(modules, dep)
}
private[this] def extractVersion(modules: Seq[ModuleRevisionId], dep: ModuleId): Option[String] =
{
modules collectFirst { case m if m.getModuleId.equals(dep) => m.getRevision }
}
private[this] def moduleRevisionIDs(report: ResolveReport): Seq[ModuleRevisionId] =
{
import collection.JavaConverters._
import org.apache.ivy.core.resolve.IvyNode
report.getDependencies.asInstanceOf[java.util.List[IvyNode]].asScala map (_.getResolvedId)
}
/** Exceptions are logged to the update log file. */
private def logExceptions(report: ResolveReport) {
for (unresolved <- report.getUnresolvedDependencies) {
val problem = unresolved.getProblem
if (problem != null)
problem.printStackTrace(logWriter)
}
}
private final class ArtifactFilter(f: IArtifact => Boolean) extends Filter {
def accept(o: Any) = o match { case a: IArtifact => f(a); case _ => false }
}
/** Retrieves resolved dependencies using the given target to determine the location to retrieve to. */
private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget, autoScalaVersion: Option[String]) {
val retrieveOptions = new RetrieveOptions
val retrieveEngine = new RetrieveEngine(settings, eventManager)
val (pattern, extraFilter) =
target match {
case _: UpdateScala => (scalaRetrievePattern, const(true))
case u: UpdateApp => (appRetrievePattern(u.id.toID), notCoreScala _)
}
val filter = (a: IArtifact) => retrieveType(a.getType) && a.getExtraAttribute("classifier") == null && extraFilter(a)
retrieveOptions.setArtifactFilter(new ArtifactFilter(filter))
val scalaV = strictOr(scalaVersion, autoScalaVersion)
retrieveOptions.setDestArtifactPattern(baseDirectoryName(scalaOrg, scalaV) + "/" + pattern)
retrieveEngine.retrieve(module.getModuleRevisionId, retrieveOptions)
}
private[this] def notCoreScala(a: IArtifact) = a.getName match {
case LibraryModuleName | CompilerModuleName => false
case _ => true
}
private def retrieveType(tpe: String): Boolean = tpe == "jar" || tpe == "bundle"
/** Add the Sonatype OSS repositories */
private def addResolvers(settings: IvySettings) {
val newDefault = new ChainResolver {
override def locate(artifact: IArtifact) =
if (hasImplicitClassifier(artifact)) null else super.locate(artifact)
}
newDefault.setName("redefined-public")
if (repositories.isEmpty) error("No repositories defined.")
for (repo <- repositories if includeRepo(repo))
newDefault.add(toIvyRepository(settings, repo))
configureCache(settings)
settings.addResolver(newDefault)
settings.setDefaultResolver(newDefault.getName)
}
// infrastructure is needed to avoid duplication between this class and the ivy/ subproject
private def hasImplicitClassifier(artifact: IArtifact): Boolean =
{
import collection.JavaConversions._
artifact.getQualifiedExtraAttributes.keys.exists(_.asInstanceOf[String] startsWith "m:")
}
// exclude the local Maven repository for Scala -SNAPSHOTs
private def includeRepo(repo: xsbti.Repository) = !(Repository.isMavenLocal(repo) && isSnapshot(getScalaVersion))
private def isSnapshot(scalaVersion: String) = scalaVersion.endsWith(Snapshot)
private[this] val Snapshot = "-SNAPSHOT"
private[this] val ChangingPattern = ".*" + Snapshot
private[this] val ChangingMatcher = PatternMatcher.REGEXP
private[this] def configureCache(settings: IvySettings) {
configureResolutionCache(settings)
configureRepositoryCache(settings)
}
private[this] def configureResolutionCache(settings: IvySettings) {
resolutionCacheBase.mkdirs()
val drcm = new DefaultResolutionCacheManager(resolutionCacheBase)
drcm.setSettings(settings)
settings.setResolutionCacheManager(drcm)
}
private[this] def configureRepositoryCache(settings: IvySettings) {
val cacheDir = settings.getDefaultRepositoryCacheBasedir()
val manager = new DefaultRepositoryCacheManager("default-cache", settings, cacheDir) {
// ignore resolvers wherever possible- not ideal, but avoids issues like #704
override def saveResolvers(descriptor: ModuleDescriptor, metadataResolverName: String, artifactResolverName: String) {}
override def findModuleInCache(dd: DependencyDescriptor, revId: ModuleRevisionId, options: CacheMetadataOptions, r: String) = {
super.findModuleInCache(dd, revId, options, null)
}
}
manager.setUseOrigin(true)
manager.setChangingMatcher(ChangingMatcher)
manager.setChangingPattern(ChangingPattern)
settings.addRepositoryCacheManager(manager)
settings.setDefaultRepositoryCacheManager(manager)
}
private def toIvyRepository(settings: IvySettings, repo: xsbti.Repository) =
{
import xsbti.Predefined._
repo match {
case m: xsbti.MavenRepository => mavenResolver(m.id, m.url.toString)
case i: xsbti.IvyRepository => urlResolver(i.id, i.url.toString, i.ivyPattern, i.artifactPattern, i.mavenCompatible, i.descriptorOptional, i.skipConsistencyCheck)
case p: xsbti.PredefinedRepository => p.id match {
case Local => localResolver(settings.getDefaultIvyUserDir.getAbsolutePath)
case MavenLocal => mavenLocal
case MavenCentral => mavenMainResolver
case ScalaToolsReleases | SonatypeOSSReleases => mavenResolver("Sonatype Releases Repository", "https://oss.sonatype.org/content/repositories/releases")
case ScalaToolsSnapshots | SonatypeOSSSnapshots => scalaSnapshots(getScalaVersion)
}
}
}
private def onDefaultRepositoryCacheManager(settings: IvySettings)(f: DefaultRepositoryCacheManager => Unit) {
settings.getDefaultRepositoryCacheManager match {
case manager: DefaultRepositoryCacheManager => f(manager)
case _ => ()
}
}
/** Uses the pattern defined in BuildConfiguration to download sbt from Google code.*/
private def urlResolver(id: String, base: String, ivyPattern: String, artifactPattern: String, mavenCompatible: Boolean, descriptorOptional: Boolean, skipConsistencyCheck: Boolean) =
{
val resolver = new URLResolver
resolver.setName(id)
resolver.addIvyPattern(adjustPattern(base, ivyPattern))
resolver.addArtifactPattern(adjustPattern(base, artifactPattern))
resolver.setM2compatible(mavenCompatible)
resolver.setDescriptor(if (descriptorOptional) BasicResolver.DESCRIPTOR_OPTIONAL else BasicResolver.DESCRIPTOR_REQUIRED)
resolver.setCheckconsistency(!skipConsistencyCheck)
resolver
}
private def adjustPattern(base: String, pattern: String): String =
(if (base.endsWith("/") || isEmpty(base)) base else (base + "/")) + pattern
private def mavenLocal = mavenResolver("Maven2 Local", "file://" + System.getProperty("user.home") + "/.m2/repository/")
/** Creates a maven-style resolver.*/
private def mavenResolver(name: String, root: String) =
{
val resolver = new IBiblioResolver
resolver.setName(name)
resolver.setM2compatible(true)
resolver.setRoot(root)
resolver
}
private def useSecureResolvers = sys.props.get("sbt.repository.secure") map { _.toLowerCase == "true" } getOrElse true
private def centralRepositoryRoot(secure: Boolean) = (if (secure) "https" else "http") + "://repo1.maven.org/maven2/"
/** Creates a resolver for Maven Central.*/
private def mavenMainResolver = defaultMavenResolver("Maven Central")
/** Creates a maven-style resolver with the default root.*/
private def defaultMavenResolver(name: String) =
mavenResolver(name, centralRepositoryRoot(useSecureResolvers))
private def localResolver(ivyUserDirectory: String) =
{
val localIvyRoot = ivyUserDirectory + "/local"
val resolver = new FileSystemResolver
resolver.setName(LocalIvyName)
resolver.addIvyPattern(localIvyRoot + "/" + LocalIvyPattern)
resolver.addArtifactPattern(localIvyRoot + "/" + LocalArtifactPattern)
resolver
}
private val SnapshotPattern = Pattern.compile("""(\d+).(\d+).(\d+)-(\d{8})\.(\d{6})-(\d+|\+)""")
private def scalaSnapshots(scalaVersion: String) =
{
val m = SnapshotPattern.matcher(scalaVersion)
if (m.matches) {
val base = List(1, 2, 3).map(m.group).mkString(".")
val pattern = "https://oss.sonatype.org/content/repositories/snapshots/[organization]/[module]/" + base + "-SNAPSHOT/[artifact]-[revision](-[classifier]).[ext]"
val resolver = new URLResolver
resolver.setName("Sonatype OSS Snapshots")
resolver.setM2compatible(true)
resolver.addArtifactPattern(pattern)
resolver
} else
mavenResolver("Sonatype Snapshots Repository", "https://oss.sonatype.org/content/repositories/snapshots")
}
/** Logs the given message to a file and to the console. */
private def log(msg: String) =
{
try { logWriter.println(msg) }
catch { case e: Exception => System.err.println("Error writing to update log file: " + e.toString) }
System.out.println(msg)
}
}
import SbtIvyLogger.{ acceptError, acceptMessage, isAlwaysIgnoreMessage }
/**
* A custom logger for Ivy to ignore the messages about not finding classes
* intentionally filtered using proguard and about 'unknown resolver'.
*/
private final class SbtIvyLogger(logWriter: PrintWriter) extends DefaultMessageLogger(Message.MSG_INFO) {
override def log(msg: String, level: Int): Unit =
if (isAlwaysIgnoreMessage(msg)) ()
else {
logWriter.println(msg)
if (level <= getLevel && acceptMessage(msg))
System.out.println(msg)
}
override def rawlog(msg: String, level: Int) { log(msg, level) }
/** This is a hack to filter error messages about 'unknown resolver ...'. */
override def error(msg: String) = if (acceptError(msg)) super.error(msg)
}
private final class SbtMessageLoggerEngine extends MessageLoggerEngine {
/** This is a hack to filter error messages about 'unknown resolver ...'. */
override def error(msg: String) = if (acceptError(msg)) super.error(msg)
}
private object SbtIvyLogger {
val IgnorePrefix = "impossible to define"
val UnknownResolver = "unknown resolver"
def acceptError(msg: String) = acceptMessage(msg) && !msg.startsWith(UnknownResolver)
def acceptMessage(msg: String) = (msg ne null) && !msg.startsWith(IgnorePrefix)
def isAlwaysIgnoreMessage(msg: String): Boolean =
(msg eq null) ||
(msg startsWith "setting 'http.proxyPassword'")
}

View File

@ -1,40 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbt.boot
import java.io.{ Closeable, File, FileInputStream, FileOutputStream, InputStream, OutputStream }
object Using {
def apply[R <: Closeable, T](create: R)(f: R => T): T = withResource(create)(f)
def withResource[R <: Closeable, T](r: R)(f: R => T): T = try { f(r) } finally { r.close() }
}
object Copy {
def apply(files: List[File], toDirectory: File): Boolean = files.map(file => apply(file, toDirectory)).contains(true)
def apply(file: File, toDirectory: File): Boolean =
{
toDirectory.mkdirs()
val to = new File(toDirectory, file.getName)
val missing = !to.exists
if (missing) {
Using(new FileInputStream(file)) { in =>
Using(new FileOutputStream(to)) { out =>
transfer(in, out)
}
}
}
missing
}
def transfer(in: InputStream, out: OutputStream) {
val buffer = new Array[Byte](8192)
def next() {
val read = in.read(buffer)
if (read > 0) {
out.write(buffer, 0, read)
next()
}
}
next()
}
}

View File

@ -1,42 +0,0 @@
/** These are packaged and published locally and the resulting artifact is used to test the launcher.*/
package xsbt.boot.test
class Exit(val code: Int) extends xsbti.Exit
final class MainException(message: String) extends RuntimeException(message)
final class ArgumentTest extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration) =
if (configuration.arguments.length == 0)
throw new MainException("Arguments were empty")
else
new Exit(0)
}
class AppVersionTest extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration) =
{
val expected = configuration.arguments.headOption.getOrElse("")
if (configuration.provider.id.version == expected)
new Exit(0)
else
throw new MainException("app version was " + configuration.provider.id.version + ", expected: " + expected)
}
}
class ExtraTest extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration) =
{
configuration.arguments.foreach { arg =>
if (getClass.getClassLoader.getResource(arg) eq null)
throw new MainException("Could not find '" + arg + "'")
}
new Exit(0)
}
}
object PlainArgumentTestWithReturn {
def main(args: Array[String]): Int =
if (args.length == 0) 1
else 0
}
object PlainArgumentTest {
def main(args: Array[String]): Unit =
if (args.length == 0) throw new MainException("Arguments were empty")
else ()
}

View File

@ -1,72 +0,0 @@
/** These are packaged and published locally and the resulting artifact is used to test the launcher.*/
package xsbt.boot.test
import java.net.Socket
import java.net.SocketTimeoutException
class EchoServer extends xsbti.ServerMain {
def start(configuration: xsbti.AppConfiguration): xsbti.Server =
{
object server extends xsbti.Server {
// TODO - Start a server.
val serverSocket = new java.net.ServerSocket(0)
val port = serverSocket.getLocalPort
val addr = serverSocket.getInetAddress.getHostAddress
override val uri = new java.net.URI(s"http://${addr}:${port}")
// Check for stop every second.
serverSocket.setSoTimeout(1000)
object serverThread extends Thread {
private val running = new java.util.concurrent.atomic.AtomicBoolean(true)
override def run(): Unit = {
while (running.get) try {
val clientSocket = serverSocket.accept()
// Handle client connections
object clientSocketThread extends Thread {
override def run(): Unit = {
echoTo(clientSocket)
}
}
clientSocketThread.start()
} catch {
case e: SocketTimeoutException => // Ignore
}
}
// Simple mechanism to dump input to output.
private def echoTo(socket: Socket): Unit = {
val input = new java.io.BufferedReader(new java.io.InputStreamReader(socket.getInputStream))
val output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(socket.getOutputStream))
import scala.util.control.Breaks._
try {
// Lame way to break out.
breakable {
def read(): Unit = input.readLine match {
case null => ()
case "kill" =>
running.set(false)
serverSocket.close()
break()
case line =>
output.write(line)
output.flush()
read()
}
read()
}
} finally {
output.close()
input.close()
socket.close()
}
}
}
// Start the thread immediately
serverThread.start()
override def awaitTermination(): xsbti.MainResult = {
serverThread.join()
new Exit(0)
}
}
server
}
}

View File

@ -18,6 +18,8 @@ object Dependencies {
lazy val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.11.4" lazy val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.11.4"
lazy val specs2 = "org.specs2" %% "specs2" % "2.3.11" lazy val specs2 = "org.specs2" %% "specs2" % "2.3.11"
lazy val junit = "junit" % "junit" % "4.11" lazy val junit = "junit" % "junit" % "4.11"
lazy val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.0-M1"
lazy val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.0-M1"
private def scala211Module(name: String, moduleVersion: String) = private def scala211Module(name: String, moduleVersion: String) =
Def.setting { Def.setting {

View File

@ -1,153 +0,0 @@
import sbt._
import Keys._
import Scope.{ GlobalScope, ThisScope }
object LaunchProguard {
lazy val Proguard = config("proguard") hide;
lazy val configurationFile = settingKey[File]("Location of the generated proguard configuration file.")
lazy val proguard = taskKey[File]("Produces the final compacted jar that contains only the classes needed using proguard.")
lazy val proguardConfiguration = taskKey[File]("Creates the configuration file to use with proguard.")
lazy val options = taskKey[Seq[String]]("Proguard options.")
lazy val optimizePasses = settingKey[Int]("Number of optimization passes proguard performs.")
lazy val keepFullClasses = settingKey[Seq[String]]("Fully qualified names of classes that proguard should preserve the non-private API of.")
lazy val settings: Seq[Setting[_]] =
inScope(GlobalScope)(inConfig(Proguard)(globalSettings)) ++
inConfig(Proguard)(baseSettings) :+
(libraryDependencies += "net.sf.proguard" % "proguard-base" % "4.8" % Proguard.name)
/** Defaults */
def globalSettings = Seq(
optimizePasses := 2, // more don't seem to help much and it increases proguard runtime
keepFullClasses := Nil,
options := basicOptions
)
def baseSettings = Seq(
optimizeSetting,
options ++= keepFullClasses.value map ("-keep public class " + _ + " {\n\tpublic protected * ;\n}"),
configurationFile := target.value / "proguard.pro",
proguardConfiguration := writeProguardConfiguration.value,
proguard := proguardTask.value
)
/** Options to set the number of optimization passes or to disable optimization altogether. */
def optimizeSetting = options ++= {
val passes = optimizePasses.value
if (passes <= 0)
Seq("-dontoptimize")
else
Seq(
"-optimizationpasses " + passes.toString,
// optimization is problematic without this option, possibly proguard can't handle certain scalac-generated bytecode
"-optimizations !code/allocation/variable"
)
}
def specific(launchSub: Reference): Seq[Setting[_]] = inConfig(Proguard)(Seq(
keepFullClasses ++= "xsbti.**" :: Nil,
artifactPath := target.value / ("sbt-launch-" + version.value + ".jar"),
options ++= dependencyOptions(launchSub).value,
options += "-injars " + mkpath(packageBin.value),
packageBin := (packageBin in (launchSub, Compile)).value,
options ++= mainClass.in(launchSub, Compile).value.toList map keepMain,
options += "-outjars " + mkpath(artifactPath.value),
fullClasspath := Classpaths.managedJars(configuration.value, classpathTypes.value, update.value)
))
def basicOptions =
Seq(
"-keep,allowoptimization,allowshrinking class * { *; }", // no obfuscation
"-keepattributes SourceFile,LineNumberTable", // preserve debugging information
"-dontnote",
"-dontwarn", // too many warnings generated for scalac-generated bytecode last time this was enabled
"-ignorewarnings")
/** Option to preserve the main entry point. */
private def keepMain(className: String) =
s"""-keep public class $className {
| public static void main(java.lang.String[]);
|}""".stripMargin
private def excludeIvyResources =
"META-INF/**" ::
"fr/**" ::
"**/antlib.xml" ::
"**/*.png" ::
"org/apache/ivy/core/settings/ivyconf*.xml" ::
"org/apache/ivy/core/settings/ivysettings-*.xml" ::
"org/apache/ivy/plugins/resolver/packager/*" ::
"**/ivy_vfs.xml" ::
"org/apache/ivy/plugins/report/ivy-report-*" ::
Nil
// libraryFilter and the Scala library-specific filtering in mapInJars can be removed for 2.11, since it is properly modularized
private def libraryFilter = "(!META-INF/**,!*.properties,!scala/util/parsing/*.class,**.class)"
private def generalFilter = "(!META-INF/**,!*.properties)"
def dependencyOptions(launchSub: Reference) = Def.task {
val cp = (dependencyClasspath in (launchSub, Compile)).value
val analysis = (compile in (launchSub, Compile)).value
mapJars(cp.files, analysis.relations.allBinaryDeps.toSeq, streams.value.log)
}
def mapJars(in: Seq[File], all: Seq[File], log: Logger): Seq[String] =
mapInJars(in, log) ++ mapLibraryJars(all filterNot in.toSet)
def writeProguardConfiguration = Def.task {
val content = options.value.mkString("\n")
val conf = configurationFile.value
if (!conf.exists || IO.read(conf) != content) {
streams.value.log.info("Proguard configuration written to " + conf)
IO.write(conf, content)
}
conf
}
def mapLibraryJars(libraryJars: Seq[File]): Seq[String] = libraryJars.map(f => "-libraryjars " + mkpath(f))
def mapOutJar(outJar: File) = "-outjars " + mkpath(outJar)
def mkpath(f: File): String = mkpath(f.getAbsolutePath, '\"')
def mkpath(path: String, delimiter: Char): String = delimiter + path + delimiter
def proguardTask = Def.task {
val inJar = packageBin.value
val outputJar = artifactPath.value
val configFile = proguardConfiguration.value
val f = FileFunction.cached(cacheDirectory.value / "proguard", FilesInfo.hash) { _ =>
runProguard(outputJar, configFile, fullClasspath.value.files, streams.value.log)
Set(outputJar)
}
f(Set(inJar, configFile)) // make the assumption that if the classpath changed, the outputJar would change
outputJar
}
def runProguard(outputJar: File, configFile: File, cp: Seq[File], log: Logger) {
IO.delete(outputJar)
val fileString = mkpath(configFile.getAbsolutePath, '\'')
val exitValue = Process("java", List("-Xmx256M", "-cp", Path.makeString(cp), "proguard.ProGuard", "-include " + fileString)) ! log
if (exitValue != 0) sys.error("Proguard failed with nonzero exit code (" + exitValue + ")")
}
def mapInJars(inJars: Seq[File], log: Logger): Seq[String] =
{
val (ivyJars, notIvy) = inJars partition isJarX("ivy")
val (libraryJar, remaining) = notIvy partition isJarX("scala-library")
val (compilerJar, otherJars) = remaining partition isJarX("scala-compiler")
log.debug("proguard configuration:")
log.debug("\tIvy jar location: " + ivyJars.mkString(", "))
log.debug("\tOther jars:\n\t" + otherJars.mkString("\n\t"))
((withJar(ivyJars.toSeq, "Ivy") + excludeString(excludeIvyResources)) ::
(withJar(libraryJar, "Scala library") + libraryFilter) ::
otherJars.map(jar => mkpath(jar) + generalFilter).toList) map { "-injars " + _ }
}
private def excludeString(s: List[String]) = s.map("!" + _).mkString("(", ",", ")")
private def withJar[T](files: Seq[File], name: String) = mkpath(files.headOption getOrElse error(name + " not present"))
private def isJarX(x: String)(file: File) =
{
val name = file.getName
name.startsWith(x) && name.endsWith(".jar")
}
}

View File

@ -0,0 +1,50 @@
import sbt._
import Keys._
object SbtLauncherPlugin extends AutoPlugin {
override def requires = plugins.IvyPlugin
object autoImport {
val SbtLaunchConfiguration = config("sbt-launch")
val sbtLaunchJar = taskKey[File]("constructs an sbt-launch.jar for this version of sbt.")
val rawSbtLaunchJar = taskKey[File]("The released version of the sbt-launcher we use to bundle this application.")
}
import autoImport._
override def projectConfigurations: Seq[Configuration] = Seq(SbtLaunchConfiguration)
override def projectSettings: Seq[Setting[_]] = Seq(
libraryDependencies += Dependencies.rawLauncher % SbtLaunchConfiguration.name,
rawSbtLaunchJar := {
Classpaths.managedJars(SbtLaunchConfiguration, Set("jar"), update.value).headOption match {
case Some(jar) => jar.data
case None => sys.error(s"Could not resolve sbt launcher!, dependencies := ${libraryDependencies.value}")
}
},
sbtLaunchJar := {
val propFiles = (resources in Compile).value
val propFileLocations =
for(file <- propFiles; if file.getName != "resources") yield {
if(file.getName == "sbt.boot.properties") "sbt/sbt.boot.properties" -> file
else file.getName -> file
}
propFileLocations foreach println
// TODO - We need to inject the appropriate boot.properties file for this version of sbt.
rebundle(rawSbtLaunchJar.value, propFileLocations.toMap, target.value / "sbt-launch.jar")
}
)
def rebundle(jar: File, overrides: Map[String, File], target: File): File = {
// TODO - Check if we should rebuild the jar or not....
IO.withTemporaryDirectory { dir =>
IO.unzip(jar, dir)
IO.copy(overrides.map({ case (n, f) => (f, dir / n)}), overwrite = true)
// TODO - is the ok for creating a jar?
IO.zip((dir.*** --- dir) x relativeTo(dir), target)
}
target
}
}

View File

@ -1,7 +1,6 @@
import sbt._ import sbt._
import Keys._ import Keys._
import Def.Initialize import Def.Initialize
import LaunchProguard._
object Scripted { object Scripted {
def scriptedPath = file("scripted") def scriptedPath = file("scripted")

View File

@ -88,9 +88,12 @@ object Transform {
def read(file: File): Option[String] = try { Some(IO.read(file)) } catch { case _: java.io.IOException => None } def read(file: File): Option[String] = try { Some(IO.read(file)) } catch { case _: java.io.IOException => None }
lazy val Property = """\$\{\{([\w.-]+)\}\}""".r lazy val Property = """\$\{\{([\w.-]+)\}\}""".r
def repositories(isSnapshot: Boolean) = Releases :: (if (isSnapshot) Snapshots :: Nil else Nil) def repositories(isSnapshot: Boolean) = Releases :: (if (isSnapshot) Snapshots :: SonatypeSnapshots :: Nil else Nil)
lazy val Releases = typesafeRepository("releases") lazy val Releases = typesafeRepository("releases")
lazy val Snapshots = typesafeRepository("snapshots") lazy val Snapshots = typesafeRepository("snapshots")
lazy val SonatypeSnapshots = sonatypeRepsoitory("snapshots")
def sonatypeRepsoitory(status: String) =
s""" sonatype-$status: https://oss.sonatype.org/content/repositories/$status"""
def typesafeRepository(status: String) = def typesafeRepository(status: String) =
""" typesafe-ivy-%s: https://repo.typesafe.com/typesafe/ivy-%<s/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly""" format status """ typesafe-ivy-%s: https://repo.typesafe.com/typesafe/ivy-%<s/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly""" format status
} }

View File

@ -51,7 +51,9 @@ object MakePomTest extends Build
withRepositories(pomXML) { repositoriesElement => withRepositories(pomXML) { repositoriesElement =>
val repositories = repositoriesElement \ "repository" val repositories = repositoriesElement \ "repository"
val writtenRepositories = repositories.map(read).distinct val writtenRepositories = repositories.map(read).distinct
val mavenStyleRepositories = ivyRepositories.collect { case x: MavenRepository if x.name != "public" => normalize(x) } distinct; val mavenStyleRepositories = ivyRepositories.collect {
case x: MavenRepository if (x.name != "public") && (x.name != "jcenter") => normalize(x)
} distinct;
lazy val explain = (("Written:" +: writtenRepositories) ++ ("Declared:" +: mavenStyleRepositories)).mkString("\n\t") lazy val explain = (("Written:" +: writtenRepositories) ++ ("Declared:" +: mavenStyleRepositories)).mkString("\n\t")