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 Licensed._
import Scope.ThisScope
import LaunchProguard.{ proguard, Proguard }
import Scripted._
import StringUtilities.normalize
import Sxr.sxr
@ -22,6 +21,7 @@ def commonSettings: Seq[Setting[_]] = Seq(
componentID := None,
crossPaths := false,
resolvers += Resolver.typesafeIvyRepo("releases"),
resolvers += Resolver.sonatypeRepo("snapshots"),
concurrentRestrictions in Global += Util.testExclusiveRestriction,
testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
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(
publish := {},
publishLocal := {
val p = (proguard in (proguardedLauncherProj, Proguard)).value
IO.copyFile(p, target.value / p.getName)
}
publishLocal := {}
)
// 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
lazy val allPrecompiled: Project = (project in file("all-precompiled")).
aggregate(precompiled282, precompiled292, precompiled293).
@ -63,55 +79,6 @@ lazy val allPrecompiled: Project = (project in file("all-precompiled")).
/* ** 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
// 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
@ -188,11 +155,11 @@ lazy val ioProj = (project in utilPath / "io").
// Utilities related to reflection, managing Scala versions, and custom class loaders
lazy val classpathProj = (project in utilPath / "classpath").
dependsOn(launchInterfaceProj, interfaceProj, ioProj).
dependsOn(interfaceProj, ioProj).
settings(testedBaseSettings: _*).
settings(
name := "Classpath",
libraryDependencies += scalaCompiler.value
libraryDependencies ++= Seq(scalaCompiler.value,Dependencies.launcherInterface)
)
// Command line-related utilities.
@ -258,20 +225,20 @@ lazy val logicProj = (project in utilPath / "logic").
// Apache Ivy integration
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(
name := "Ivy",
libraryDependencies ++= Seq(ivy, jsch, sbtSerialization),
libraryDependencies ++= Seq(ivy, jsch, sbtSerialization, launcherInterface),
testExclusive)
// Runner for uniform test interface
lazy val testingProj = (project in file("testing")).
dependsOn(ioProj, classpathProj, logProj, launchInterfaceProj, testAgentProj).
dependsOn(ioProj, classpathProj, logProj, testAgentProj).
settings(baseSettings: _*).
settings(
name := "Testing",
libraryDependencies += testInterface
libraryDependencies ++= Seq(testInterface,launcherInterface)
)
// 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.
// Includes API and Analyzer phases that extract source API and relationships.
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(
name := "Compiler Interface",
@ -366,12 +333,12 @@ lazy val compilePersistProj = (project in compilePath / "persist").
// sbt-side interface to compiler. Calls compiler-side interface reflectively
lazy val compilerProj = (project in compilePath).
dependsOn(launchInterfaceProj, interfaceProj % "compile;test->test", logProj, ioProj, classpathProj, apiProj, classfileProj,
logProj % "test->test", launchProj % "test->test").
dependsOn(interfaceProj % "compile;test->test", logProj, ioProj, classpathProj, apiProj, classfileProj,
logProj % "test->test" /*,launchProj % "test->test" */).
settings(testedBaseSettings: _*).
settings(
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)
)
@ -398,10 +365,11 @@ lazy val scriptedBaseProj = (project in scriptedPath / "base").
)
lazy val scriptedSbtProj = (project in scriptedPath / "sbt").
dependsOn (ioProj, logProj, processProj, scriptedBaseProj, launchInterfaceProj % "provided").
dependsOn (ioProj, logProj, processProj, scriptedBaseProj).
settings(baseSettings: _*).
settings(
name := "Scripted sbt"
name := "Scripted sbt",
libraryDependencies += launcherInterface % "provided"
)
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
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(
name := "Command"
name := "Command",
libraryDependencies += launcherInterface
)
// 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.
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(
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
@ -471,13 +440,13 @@ lazy val mavenResolverPluginProj = (project in file("sbt-maven-resolver")).
def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask {
val result = scriptedSource(dir => (s: State) => scriptedParser(dir)).parsed
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)
}
def scriptedUnpublishedTask: Initialize[InputTask[Unit]] = Def.inputTask {
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)
}
@ -486,8 +455,7 @@ lazy val publishLauncher = TaskKey[Unit]("publish-launcher")
lazy val myProvided = config("provided") intransitive
def allProjects = Seq(launchInterfaceProj, launchProj, proguardedLauncherProj,
testSamples, interfaceProj, apiProj,
def allProjects = Seq(interfaceProj, apiProj,
controlProj, collectionProj, applyMacroProj, processProj, ioProj, classpathProj, completeProj,
logProj, relationProj, classfileProj, datatypeProj, crossProj, logicProj, ivyProj,
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))
def rootSettings = Release.releaseSettings ++ fullDocSettings ++
Util.publishPomSettings ++ otherRootSettings ++ Formatting.sbtFilesSettings ++
Transform.conscriptSettings(launchProj)
Util.publishPomSettings ++ otherRootSettings ++ Formatting.sbtFilesSettings /*++
Transform.conscriptSettings(launchProj)*/
def otherRootSettings = Seq(
Scripted.scriptedPrescripted := { _ => },
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 safeProjects: ScopeFilter = ScopeFilter(
inProjects(launchProj, mainSettingsProj, mainProj, ivyProj, completeProj,
inProjects(mainSettingsProj, mainProj, ivyProj, completeProj,
actionsProj, classpathProj, collectionProj, compileIncrementalProj,
logProj, runProj, stdTaskProj),
inConfigurations(Test)
@ -639,7 +607,7 @@ def customCommands: Seq[Setting[_]] = Seq(
"conscript-configs" ::
"so compile" ::
"so publishSigned" ::
"publishLauncher" ::
"bundledLauncherProj/publishLauncher" ::
state
},
// 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 mavenRepositories =
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 =>
MavenRepository(m.repo.name, m.repo.root) :: Nil
case m: IBiblioResolver if m.isM2compatible && m.getRoot != DefaultMavenRepository.root =>

View File

@ -1,11 +1,17 @@
package sbt
import java.io.File
import java.util.concurrent.Callable
import org.specs2._
import mutable.Specification
import IO.{ createDirectory, delete, touch, withTemporaryDirectory }
import org.apache.ivy.util.ChecksumHelper
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 {
val TestID = "manager-test"
@ -82,7 +88,39 @@ object ComponentManagerTest extends Specification {
private def withManagerHome[T](ivyHome: File)(f: ComponentManager => T): T =
TestLogger { logger =>
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)
}
}

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]
local
jcenter: https://jcenter.bintray.com/
${{repositories}}
maven-central
[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 specs2 = "org.specs2" %% "specs2" % "2.3.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) =
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 Keys._
import Def.Initialize
import LaunchProguard._
object 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 }
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 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) =
""" 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 =>
val repositories = repositoriesElement \ "repository"
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")