mirror of https://github.com/sbt/sbt.git
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:
commit
74a9c047f8
124
build.sbt
124
build.sbt
|
|
@ -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".
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
Simple Build Tool: Launcher Interface Component
|
||||
Copyright 2009, 2010 Mark Harrah
|
||||
Licensed under BSD-style license (see LICENSE)
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package xsbti;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface AppConfiguration
|
||||
{
|
||||
public String[] arguments();
|
||||
public File baseDirectory();
|
||||
public AppProvider provider();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package xsbti;
|
||||
|
||||
public enum CrossValue
|
||||
{
|
||||
Disabled, Full, Binary
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package xsbti;
|
||||
|
||||
enum Manage
|
||||
{
|
||||
Nop, Clean, Refresh
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package xsbti;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public interface MavenRepository extends Repository
|
||||
{
|
||||
String id();
|
||||
URL url();
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package xsbti;
|
||||
|
||||
public interface PredefinedRepository extends Repository
|
||||
{
|
||||
Predefined id();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package xsbti;
|
||||
|
||||
public interface Repository {}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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/}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + ")"))
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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'")
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import sbt._
|
||||
import Keys._
|
||||
import Def.Initialize
|
||||
import LaunchProguard._
|
||||
|
||||
object Scripted {
|
||||
def scriptedPath = file("scripted")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue