Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

New generalized launcher

  • Loading branch information...
commit 56e96c3f49ece6b4255e52385069c45376753c54 1 parent 76e8140
@harrah authored
Showing with 846 additions and 495 deletions.
  1. +78 −0 launch.specification
  2. +0 −1  launch/Boot.scala
  3. +16 −30 launch/BootConfiguration.scala
  4. +44 −0 launch/Configuration.scala
  5. +174 −0 launch/ConfigurationParser.scala
  6. +64 −169 launch/Launch.scala
  7. +91 −0 launch/LaunchConfiguration.scala
  8. +0 −147 launch/ProjectProperties.scala
  9. +56 −0 launch/Provider.scala
  10. +75 −0 launch/ResolveVersions.scala
  11. +32 −15 launch/SimpleReader.scala
  12. +71 −66 launch/Update.scala
  13. +40 −0 launch/Using.scala
  14. +10 −0 launch/interface/src/main/java/xsbti/AppConfiguration.java
  15. +6 −0 launch/interface/src/main/java/xsbti/AppMain.java
  16. +15 −0 launch/interface/src/main/java/xsbti/AppProvider.java
  17. +12 −0 launch/interface/src/main/java/xsbti/ApplicationID.java
  18. +2 −17 launch/interface/src/main/java/xsbti/Launcher.java
  19. +6 −0 launch/interface/src/main/java/xsbti/Reboot.java
  20. +0 −10 launch/interface/src/main/java/xsbti/SbtConfiguration.java
  21. +0 −6 launch/interface/src/main/java/xsbti/SbtMain.java
  22. +0 −12 launch/interface/src/main/java/xsbti/SbtProvider.java
  23. +6 −2 launch/interface/src/main/java/xsbti/ScalaProvider.java
  24. +25 −0 launch/src/main/resources/sbt/sbt.boot.properties
  25. +11 −11 notes
  26. +2 −2 project/build.properties
  27. +1 −1  project/build/ProguardProject.scala
  28. +8 −5 project/build/XSbt.scala
  29. +1 −1  tasks/src/test/scala/TestRunnerSort.scala
View
78 launch.specification
@@ -0,0 +1,78 @@
+= Launcher Specification =
+
+The sbt launcher component is a self-contained jar that boots a Scala application without Scala or the application already existing on the system. The only prerequisites are the launcher jar itself, an optional configuration file, and a java runtime version 1.5 or greater.
+
+== Configuration ==
+
+The launcher may be configured in the following ways in increasing order of precedence:
+ * Replace the /sbt/sbt.launch.config file in the jar
+ * Put a configuration file named sbt.launch.config file on the classpath. Put it in the classpath root without the /sbt prefix.
+ * Specify the location of an alternate configuration on the command line. The alternate configuration option is specified as the first argument to the launcher. This option is the path to the configuration file preceeded by '@'. Resolution of a relative path is first attempted against the current working directory, then against the user's home directory, and then against the directory containing the launcher jar. An error is generated if none of these attempts succeed.
+
+The configuration file is read as UTF-8 encoded and is defined by the following grammer (nl is a newline or end of file):
+
+configuration ::= scala app repositories boot log
+ scala ::= '[' 'scala' ']' nl versionSpecification nl
+ app ::= '[' 'app' ']' nl org nl name nl versionSpecification nl components nl class nl cross-versioned nl
+ repositories ::= '[' 'repositories' ']' nl (repository nl)*
+ boot ::= '[' 'boot' ']' nl directory nl properties nl
+ log ::= '[' log ']' nl logLevel nl
+
+ directory ::= 'directory' ':' path
+ properties ::= 'properties' ':' path
+ logLevel ::= 'log-level' ':' ('debug' | 'info' | 'warn' | 'error')
+
+ versionSpecification ::= 'version' ':' ( ( ('read'|'prompt'|'read-or-prompt') [defaultVersion] ) | fixedVersion )
+ defaultVersion ::= text
+ fixedVersion ::= text
+
+ org ::= 'org' ':' text
+ name ::= 'name' ':' text
+ class ::= 'class' ':' text
+ components ::= 'components' ':' component (',' component)*
+ cross-versioned ::= 'cross-versioned' ':' ('true' | 'false')
+
+ repository ::= ( predefinedRepository | ( label ':' url [',' pattern] ) ) nl
+ predefinedRepository ::= 'local' | 'maven-local' | 'maven-central' | 'scala-tools-releases' | 'scala-tools-snapshots'
+
+The default configuration file for sbt looks like:
+
+[scala]
+ version: read-or-prompt, 2.7.5
+
+[app]
+ org: org.scala-tools.sbt
+ name: xsbt
+ version: read-or-prompt, 0.7.0_13
+ class: xsbt.Main
+ components: xsbti, default
+ cross-versioned: true
+
+[repositories]
+ local
+ maven-local
+ Sbt Repository, http://simple-build-tool.googlecode.com/svn/artifacts/, [revision]/[type]s/[artifact].[ext]
+ maven-central
+ scala-tools-releases
+ scala-tools-snapshots
+
+[boot]
+ directory: project/boot
+ properties: project/build.properties
+
+[log]
+ level: info
+
+The scala.version property specifies the version of Scala used to run the application. The app.org, app.name, and app.version properties specify the organization, module ID, and version of the application, respectively. These are used to resolve and retrieve the application from the repositories listed in [repositories]. If cross-versioned is true, the resolved module is {app.name+'_'+scala.version}
+
+The app.class property specifies the name of the entry point to the application. An application entry point must be a public class with a no-argument constructor and conform to xsbti.SbtMain. The SbtMain interface specifies the entry method signature 'run'. The run method is passed an instance of SbtConfiguration, which provides access to the startup environment.
+
+== Execution ==
+
+On startup, the launcher searches for its configuration in the order described in the Configuration section and then parses it. If either the Scala version or the application version are implicit (read, prompt, or read-or-prompt), the launcher determines them in the following manner. If the implicit is specified to be 'read', the file given by 'boot.properties' is read as a Java properties file to obtain the version. The property names are [name].version for the application version (where [name] is replaced with app.name) and scala.version for the Scala version. If the file does not exist, the default value provided is used. If no default was provided, an error is generated. If the implicit is 'prompt', the user is prompted for the version to use and is provided a default option if one was specified. If the implicit is 'read-or-prompt', the file given by 'boot.properties' is read. If the version is not specified there, the user is prompted and is provided a default option if one was specified. The 'boot.properties' file is updated with the value specified by the user.
+
+Once the final configuration is resolved, the launcher proceeds to obtain the necessary jars to launch the application. The 'boot.directory' property is used as a base directory to retrieve jars to. No locking is done on the directory, so it should not be shared system-wide. The launcher retrieves the requested version of Scala to [boot.directory]/[scala.version]/lib/. If this directory already exists, the launcher takes a shortcut for performance and assumes that the jars have already been downloaded. If the directory does not exists, the launcher uses Apache Ivy to resolve and retrieve the jars. A similar process occurs for the application itself. It and its dependencies are retreived to [boot.directory]/[scala.version]/[app.org]/[app.name]/.
+
+Once all required code is downloaded, the class loaders are set up. The launcher creates a class loader for the requested version of Scala. It then creates a child class loader containing the jars for the requested 'app.components'. An application that does not use components will have all of its jars in this class loader.
+
+The main class for the application is then instantiated. It must be a public class with a public no-argument constructor and must conform to xsbti.AppMain. The `run` method is invoked and execution passes to the application. The argument to the 'run' method provides configuration information and a callback to obtain a class loader for any version of Scala that can be obtained from a repository in [repositories]. The return value of the run method determines what is done after the application executes. It can specify that the launcher should restart the application or that it should exit with the provided exit code.
View
1  launch/Boot.scala
@@ -3,7 +3,6 @@
*/
package xsbt.boot
-import BootConfiguration.{SbtMainClass, SbtModuleName}
import java.io.File
// The entry point to the launcher
View
46 launch/BootConfiguration.scala
@@ -3,23 +3,20 @@
*/
package xsbt.boot
-// project/boot/ [BootDirectoryName]
-// scala-<version>/ [baseDirectoryName]
-// lib/ [ScalaDirectoryName]
-// sbt-<version>/ [sbtDirectoryName]
+// <boot.directory>
+// 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
private object BootConfiguration
{
- val SbtMainClass = "xsbt.Main"
-
- // these are the module identifiers to resolve/retrieve
+ // these are the Scala module identifiers to resolve/retrieve
val ScalaOrg = "org.scala-lang"
- val SbtOrg = "org.scala-tools.sbt"
val CompilerModuleName = "scala-compiler"
val LibraryModuleName = "scala-library"
- val SbtModuleName = "xsbt"
- val MainSbtComponentID = "default"
+
+ val SbtOrg = "org.scala-tools.sbt"
/** The Ivy conflict manager to use for updating.*/
val ConflictManagerName = "strict"
@@ -32,47 +29,36 @@ private object BootConfiguration
/** The Ivy pattern used for the local Ivy repository.*/
def LocalIvyPattern = LocalPattern
- /** The class name prefix used to hide the Scala classes used by this loader from sbt
- * and the project definition*/
+ /** The class name prefix used to hide the Scala classes used by this loader from the application */
val ScalaPackage = "scala."
- /** The class name prefix used to hide the Ivy classes used by this loader from sbt
- * and the project definition*/
+ /** The class name prefix used to hide the Ivy classes used by this loader from the application*/
val IvyPackage = "org.apache.ivy."
- /** The class name prefix used to hide the sbt launcher classes from sbt and the project definition.
+ /** The class name prefix used to hide the launcher classes from the application.
* Note that access to xsbti classes are allowed.*/
val SbtBootPackage = "xsbt.boot."
/** The prefix for JLine resources.*/
val JLinePackagePath = "jline/"
/** The loader will check that these classes can be loaded and will assume that their presence indicates
- * sbt and its dependencies have been downloaded.*/
- val TestLoadSbtClasses = "xsbt.Main" :: "org.apache.ivy.Ivy" :: Nil
- /** 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.ScalaObject" :: "scala.tools.nsc.GenericRunnerCommand" :: Nil
+ val TestLoadScalaClasses = "scala.List" :: "scala.tools.nsc.GenericRunnerCommand" :: Nil
- val ProjectDirectoryName = "project"
- val BootDirectoryName = "boot"
- val BuildPropertiesName ="build.properties"
val ScalaHomeProperty = "scala.home"
val UpdateLogName = "update.log"
val DefaultIvyConfiguration = "default"
- /** The base URL to use to resolve sbt for download. */
- val sbtRootBase = "http://simple-build-tool.googlecode.com/svn/artifacts/"
/** 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].[ext]"
- /** The Ivy pattern to use for retrieving sbt and its dependencies. It is relative to the directory
+ /** 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 sbtRetrievePattern(sbtVersion: String) = sbtDirectoryName(sbtVersion) + "(/[component])/[artifact]-[revision].[ext]"
- /** The Ivy pattern to use for resolving sbt and its dependencies from the Google code project.*/
- def sbtResolverPattern(scalaVersion: String) = sbtRootBase + "[revision]/[type]s/[artifact].[ext]"
- /** The name of the directory to retrieve sbt and its dependencies to.*/
- def sbtDirectoryName(sbtVersion: String) = SbtModuleName + "-" + sbtVersion
+ def appRetrievePattern(appID: xsbti.ApplicationID) = appDirectoryName(appID, "/") + "(/[component])/[artifact]-[revision].[ext]"
+
+ /** 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(scalaVersion: String) = "scala-" + scalaVersion
}
View
44 launch/Configuration.scala
@@ -0,0 +1,44 @@
+package xsbt.boot
+
+import java.io.{File, FileInputStream, InputStreamReader}
+import java.net.{URI, URL}
+
+object Configuration
+{
+ def parse(file: URL, baseDirectory: File) = Using( new InputStreamReader(file.openStream, "utf8") )( (new ConfigurationParser(baseDirectory)).apply )
+ def find(args: Seq[String], baseDirectory: File): (URL, Seq[String]) =
+ args match
+ {
+ case Seq(head, tail @ _*) if head.startsWith("@")=> (configurationFromFile(head.substring(1), baseDirectory), tail)
+ case _ => (configurationOnClasspath, args)
+ }
+ def configurationOnClasspath: URL =
+ {
+ resourcePaths.toStream.map(getClass.getResource).find(_ ne null) getOrElse
+ ( throw new BootException(resourcePaths.mkString("Could not finder sbt launch configuration. (Searched classpath for ", ",", ")")) )
+ }
+ def configurationFromFile(path: String, baseDirectory: File): URL =
+ {
+ def resolve(against: URI): Option[URL] =
+ {
+ val resolved = against.resolve(path)
+ val exists = try { (new File(resolved)).exists } catch { case _: IllegalArgumentException => false }
+ if(exists) Some(resolved.toURL) else None
+ }
+ resolveAgainst(baseDirectory).toStream.flatMap(resolve).firstOption.getOrElse(throw new BootException("Could not find configuration file '" + path + "'."))
+ }
+
+ val ConfigurationFilename = "sbt.boot.properties"
+ val JarBasePath = "/sbt/"
+ def userConfigurationPath = "/" + ConfigurationFilename
+ def defaultConfigurationPath = JarBasePath + ConfigurationFilename
+ def resourcePaths = Seq(userConfigurationPath, defaultConfigurationPath)
+ def resolveAgainst(baseDirectory: File) = Seq(baseDirectory toURI, new File(System.getProperty("user.home")) toURI, classLocation(getClass).toURI)
+
+ def classLocation(cl: Class[_]): URL =
+ {
+ val codeSource = cl.getProtectionDomain.getCodeSource
+ if(codeSource == null) throw new BootException("No class location for " + cl)
+ else codeSource.getLocation
+ }
+}
View
174 launch/ConfigurationParser.scala
@@ -0,0 +1,174 @@
+package xsbt.boot
+
+import scala.util.parsing.combinator.Parsers
+import scala.util.parsing.input.{Reader, StreamReader}
+
+import java.lang.Character.isWhitespace
+import java.io.File
+
+class ConfigurationParser(workingDirectory: File) extends Parsers with NotNull
+{
+ def this() = this(new File("."))
+ import scala.util.parsing.input.CharSequenceReader
+ def apply(s: String): LaunchConfiguration = processResult(configuration(new CharSequenceReader(s, 0)))
+ def apply(source: java.io.Reader): LaunchConfiguration = processResult(configuration(StreamReader(source)))
+
+ def processResult(r: ParseResult[LaunchConfiguration]) =
+ r match
+ {
+ case Success(r, _) => r
+ case _ => throw new BootException(r.toString)
+ }
+
+ // section -> configuration instance processing
+ lazy val configuration = phrase(sections ^^ processSections)
+ def processSections(sections: SectionMap): LaunchConfiguration =
+ {
+ val (scalaVersion, m1) = processSection(sections, "scala", getScalaVersion)
+ val (app, m2) = processSection(m1, "app", getApplication)
+ val (repositories, m3) = processSection(m2, "repositories", getRepositories)
+ val (boot, m4) = processSection(m3, "boot", getBoot)
+ val (logging, m5) = processSection(m4, "log", getLogging)
+ check(m5, "section")
+ new LaunchConfiguration(scalaVersion, app, repositories, boot, logging)
+ }
+ def getScalaVersion(m: LabelMap) = check("label", getVersion(m))
+ def getVersion(m: LabelMap): (Version, LabelMap) = process(m, "version", processVersion)
+ def processVersion(value: Option[String]): Version = value.map(version).getOrElse(Version.default)
+ def version(value: String): Version =
+ {
+ if(value.isEmpty) error("Version cannot be empty (omit version declaration to use the default version)")
+ val tokens = trim(value.split(",", 2))
+ import Version.{Explicit, Implicit}
+ val defaultVersion = if(tokens.length == 2) Some(tokens(1)) else None
+ Implicit(tokens(0), defaultVersion).fold(err => Explicit(tokens(0)), identity[Implicit])
+ }
+ def processSection[T](sections: Map[String, LabelMap], name: String, f: LabelMap => T) =
+ process[String,LabelMap,T](sections, name, m => f(m withDefaultValue(None)))
+ def process[K,V,T](sections: Map[K,V], name: K, f: V => T): (T, Map[K,V]) = ( f(sections(name)), sections - name)
+ def check(map: Map[String, _], label: String): Unit = if(map.isEmpty) () else { error(map.keys.mkString("Invalid " + label + "(s): ", ",","")) }
+ def check[T](label: String, pair: (T, Map[String, _])): T = { check(pair._2, label); pair._1 }
+ def id(map: Map[String, Option[String]], name: String, default: String): (String, LabelMap) =
+ (map.getOrElse(name, None).getOrElse(default), map - name)
+ def ids(map: Map[String, Option[String]], name: String, default: Seq[String]) =
+ {
+ val result = map(name).map(value => trim(value.split(",")).filter(!_.isEmpty)).getOrElse(default)
+ (result, map - name)
+ }
+ def file(map: LabelMap, name: String, default: File): (File, LabelMap) =
+ (map.getOrElse(name, None).map(p => new File(p)).getOrElse(default), map - name)
+
+ def getBoot(m: LabelMap): BootSetup =
+ {
+ val (dir, m1) = file(m, "directory", new File(workingDirectory, "project/boot"))
+ val (props, m2) = file(m1, "properties", new File("project/build.properties"))
+ check(m2, "label")
+ BootSetup(dir, props)
+ }
+ def getLogging(m: LabelMap): Logging = check("label", process(m, "level", getLevel))
+ def getLevel(m: Option[String]) = m.map(l => LogLevel(l).fold(error, identity[Logging]) ).getOrElse(Logging(LogLevel.Info))
+
+ def getApplication(m: LabelMap): Application =
+ {
+ val (org, m1) = id(m, "org", "org.scala-tools.sbt")
+ val (name, m2) = id(m1, "name", "sbt")
+ val (rev, m3) = getVersion(m2)
+ val (main, m4) = id(m3, "class", "xsbt.Main")
+ val (components, m5) = ids(m4, "components", Seq("default"))
+ val (crossVersioned, m6) = id(m5, "cross-versioned", "true")
+ check(m6, "label")
+ new Application(org, name, rev, main, components, crossVersioned.toBoolean)
+ }
+ def getRepositories(m: LabelMap): Seq[Repository] =
+ {
+ import Repository.{Ivy, Maven, Predefined}
+ m.toSeq.map {
+ case (key, None) => Predefined(key).fold(err => error(err), x => x)
+ case (key, Some(value)) =>
+ val r = trim(value.split(",",2))
+ val url = r(0)
+ if(url.isEmpty) error("No URL specified for '" + key + "'")
+ if(r.length == 2) Ivy(key, url, r(1)) else Maven(key, url)
+ }
+ }
+ def trim(s: Array[String]) = s.map(_.trim)
+
+ // line parsing
+
+ def sections = lines ^^ processLines
+ type LabelMap = Map[String, Option[String]]
+ // section-name -> label -> value
+ type SectionMap = Map[String, LabelMap]
+ def processLines(lines: List[Line]): SectionMap =
+ {
+ type State = (SectionMap, Option[String])
+ val s: State =
+ ( ( (Map.empty withDefaultValue(Map.empty), None): State) /: lines ) {
+ case (x, Comment) => x
+ case ( (map, _), Section(name) ) => (map, Some(name))
+ case ( (_, None), l: Labeled ) => 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) ) error("Duplicate label '" + l.label + "' in section '" + section + "'")
+ else ( map(section) = (sMap(l.label) = l.value), s )
+ }
+ s._1
+ }
+
+ // character parsing
+ type Elem = Char
+ def lines = (line*) <~ ws_nl
+ def line: Parser[Line] = (ws_nl ~> (comment | section | labeled | failure("Expected comment, start of section, or section entry")) ~! nl ) ^^ { case m ~ _ => m }
+ def comment = '#' ~! value ^^ { x => Comment }
+ def section = ('[' ~! ws) ~! ID ~! (ws ~! ']' ~! ws) ^^ { case _ ~ i ~ _ => Section(i)}
+ def labeled = ID ~! ws ~! ((':' ~! value ^^ { case _ ~ v => v })?) >> {
+ case k ~ _ ~ Some(v) =>
+ val trimmed = v.trim
+ if(trimmed.isEmpty) failure("Value for '" + k + "' was empty") else success(Labeled(k, Some(v.trim)))
+ case k ~ _ ~ None => success(Labeled(k, None))
+ }
+
+ def ws_nl = string(isWhitespace)
+ lazy val ws = string(c => isWhitespace(c) && !isNewline(c))
+
+ def ID = IDword ~! ((ws ~> IDword)*) ^^ { case (x ~ y) => (x :: y).mkString(" ") }
+ def IDword = elem("Identifier", isIDStart) ~! string(isIDChar) ^^ { case x ~ xs => x + xs }
+ def value = string(c => !isNewline(c))
+
+ def isNewline(c: Char) = c == '\r' || c == '\n'
+ def isIDStart(c: Char) = isIDChar(c) && c != '[' && c != '#'
+ def isIDChar(c: Char) = !isWhitespace(c) && c != ':' && c != ']' && c != CharSequenceReader.EofCh
+
+ case class string(accept: Char => Boolean) extends Parser[String] with NotNull
+ {
+ def apply(in: Reader[Char]) =
+ {
+ val buffer = new StringBuilder
+ def fill(in: Reader[Char]): ParseResult[String] =
+ {
+ if(in.atEnd || !accept(in.first))
+ Success(buffer.toString, in)
+ else
+ {
+ buffer += in.first
+ fill(in.rest)
+ }
+ }
+ fill(in)
+ }
+ }
+ def nl = new Parser[Unit] with NotNull
+ {
+ def apply(in: Reader[Char]) =
+ {
+ if(in.atEnd) Success( (), in)
+ else if(isNewline(in.first)) Success( (), in.rest )
+ else Failure("Expected end of line", in)
+ }
+ }
+}
+
+sealed trait Line extends NotNull
+final case class Labeled(label: String, value: Option[String]) extends Line
+final case class Section(name: String) extends Line
+object Comment extends Line
View
233 launch/Launch.scala
@@ -1,190 +1,85 @@
-/* sbt -- Simple Build Tool
- * Copyright 2009 Mark Harrah
- */
- package xsbt.boot
+package xsbt.boot
-// This is the main class for the sbt launcher. It reads the project/build.properties file to determine the
-// version of Scala and sbt requested for the project definition. It downloads the requested version of
-// Scala, sbt, and dependencies to the project's 'project/boot' directory. It loads the main sbt and then
-// satisfies requests from the main sbt for different versions of Scala for use in the build.
+import java.io.File
-import java.io.{File, FileFilter}
-import java.net.{URL, URLClassLoader}
-
-import xsbti.{Exit => IExit, Launcher, MainResult, Reboot, SbtConfiguration, SbtMain}
-
-// contains constants and paths
-import BootConfiguration._
-import UpdateTarget.{UpdateScala, UpdateSbt}
-
-
-class Launch(val BaseDirectory: File, mainClassName: String) extends Launcher with NotNull
+object Launch
{
- def this(baseDirectory: File) = this(baseDirectory, SbtMainClass)
- def this() = this(new File("."))
-
- import Launch._
- final def boot(args: Array[String])
- {
- checkAndLoad(args) match
- {
- case e: IExit => System.exit(e.code)
- case r: Reboot => boot(r.arguments())
- case x => println("Unknown result (" + x.getClass + "): " + x); System.exit(1)
- }
- }
- def checkAndLoad(args: Array[String]): MainResult =
+ def apply(arguments: Seq[String]): Unit = apply( (new File("")).getAbsoluteFile, arguments )
+ def apply(currentDirectory: File, arguments: Seq[String])
{
- // prompt to create project if it doesn't exist.
- checkProject() match
- {
- case Some(result) => result
- case None => load(args)
- }
+ val (configFile, newArguments) = Configuration.find(arguments, currentDirectory)
+ val parsed = Configuration.parse(configFile, currentDirectory)
+ val (explicit, finish) = ResolveVersions(parsed)
+ val launcher = new Launch(explicit)
+ launch(launcher, explicit.getScalaVersion, explicit.app.toID, currentDirectory, newArguments, finish)
}
- /** Loads the project in the current working directory using the version of scala and sbt
- * declared in the build. The class loader used prevents the Scala and Ivy classes used by
- * this loader from being seen by the loaded sbt/project.*/
- def load(args: Array[String]): MainResult =
+ final def launch(launcher: xsbti.Launcher, scalaVersion: String, app: xsbti.ApplicationID, workingDirectory: File, arguments: Seq[String], finish: () => Unit)
{
- val (scalaVersion, sbtVersion) = ProjectProperties.forcePrompt(PropertiesFile)//, forcePrompt : _*)
- load(args, sbtVersion, mainClassName, scalaVersion)
- }
- def componentLocation(sbtVersion: String, id: String, scalaVersion: String): File = new File(getSbtHome(sbtVersion, scalaVersion), id)
- def getSbtHome(sbtVersion: String, scalaVersion: String): File =
- {
- val baseDirectory = new File(BootDirectory, baseDirectoryName(scalaVersion))
- new File(baseDirectory, sbtDirectoryName(sbtVersion))
- }
- def getScalaHome(scalaVersion: String) = new File(new File(BootDirectory, baseDirectoryName(scalaVersion)), ScalaDirectoryName)
+ val scalaProvider: xsbti.ScalaProvider = launcher.getScala(scalaVersion)
+ val appProvider: xsbti.AppProvider = scalaProvider.app(app)
+ val appConfig: xsbti.AppConfiguration = new AppConfiguration(arguments.toArray, workingDirectory, appProvider)
- def load(args: Array[String], useSbtVersion: String, mainClassName: String, definitionScalaVersion: String): MainResult =
- {
- val sbtLoader = update(definitionScalaVersion, useSbtVersion)
- val configuration = new SbtConfiguration
+ val main = appProvider.newMain()
+ finish()
+ main.run(appConfig) match
{
- def arguments = args
- def scalaVersion = definitionScalaVersion
- def sbtVersion = useSbtVersion
- def launcher: Launcher = Launch.this
- def baseDirectory = BaseDirectory
+ case e: xsbti.Exit => System.exit(e.code)
+ case r: xsbti.Reboot => launch(launcher, r.scalaVersion, r.app, r.baseDirectory, r.arguments, () => ())
+ case x => throw new BootException("Invalid main result: " + x + (if(x eq null) "" else " (class: " + x.getClass + ")"))
}
- run(sbtLoader, mainClassName, configuration)
- }
- def update(scalaVersion: String, sbtVersion: String): ClassLoader =
- {
- val scalaLoader = getScalaLoader(scalaVersion)
- createSbtLoader(sbtVersion, scalaVersion, scalaLoader)
}
+}
- def run(sbtLoader: ClassLoader, mainClassName: String, configuration: SbtConfiguration): MainResult =
+import BootConfiguration.{appDirectoryName, baseDirectoryName, ScalaDirectoryName, TestLoadScalaClasses}
+class Launch(val configuration: LaunchConfiguration) extends xsbti.Launcher
+{
+ import scala.collection.mutable.HashMap
+ private val scalaProviders = new HashMap[String, ScalaProvider]
+ def getScala(version: String): xsbti.ScalaProvider = scalaProviders.getOrElseUpdate(version, new ScalaProvider(version))
+
+ class ScalaProvider(val version: String) extends xsbti.ScalaProvider with Provider
{
- val sbtMain = Class.forName(mainClassName, true, sbtLoader)
- val main = sbtMain.newInstance.asInstanceOf[SbtMain]
- main.run(configuration)
- }
+ def launcher: xsbti.Launcher = Launch.this
- final val ProjectDirectory = new File(BaseDirectory, ProjectDirectoryName)
- final val BootDirectory = new File(ProjectDirectory, BootDirectoryName)
- final val PropertiesFile = new File(ProjectDirectory, BuildPropertiesName)
+ lazy val parentLoader = new BootFilteredLoader(getClass.getClassLoader)
+ lazy val configuration = Launch.this.configuration.withScalaVersion(version)
+ lazy val libDirectory = new File(configuration.boot.directory, baseDirectoryName(version))
+ lazy val scalaHome = new File(libDirectory, ScalaDirectoryName)
+ def baseDirectories = Seq(scalaHome)
+ def testLoadClasses = TestLoadScalaClasses
+ def target = UpdateTarget.UpdateScala
+ def failLabel = "Scala " + version
- final def checkProject() =
- {
- if(ProjectDirectory.exists)
- None
- else
+ def app(id: xsbti.ApplicationID): xsbti.AppProvider = new AppProvider(id)
+
+ class AppProvider(val id: xsbti.ApplicationID) extends xsbti.AppProvider with Provider
{
- val line = SimpleReader.readLine("Project does not exist, create new project? (y/N/s) : ")
- if(isYes(line))
+ def scalaProvider: xsbti.ScalaProvider = ScalaProvider.this
+ lazy val configuration = ScalaProvider.this.configuration.withApp(Application(id))
+ lazy val appHome = new File(libDirectory, appDirectoryName(id, File.separator))
+ def parentLoader = ScalaProvider.this.loader
+ def baseDirectories = id.mainComponents.map(componentLocation) ++ Seq(appHome)
+ def testLoadClasses = Seq(id.mainClass)
+ def target = UpdateTarget.UpdateApp
+ def failLabel = id.name + " " + id.version
+
+ lazy val mainClass: Class[T] forSome { type T <: xsbti.AppMain } =
{
- ProjectProperties(PropertiesFile, true)
- None
+ val c = Class.forName(id.mainClass, true, loader)
+ c.asSubclass(classOf[xsbti.AppMain])
}
- else if(isScratch(line))
+ def newMain(): xsbti.AppMain = mainClass.newInstance
+
+ def componentLocation(id: String): File = new File(appHome, id)
+ def component(id: String) = GetJars(componentLocation(id) :: Nil)
+ def defineComponent(id: String, files: Array[File]) =
{
- ProjectProperties.scratch(PropertiesFile)
- None
+ val location = componentLocation(id)
+ if(location.exists)
+ throw new BootException("Cannot redefine component. (id: " + id + ", files: " + files.mkString(","))
+ else
+ files.foreach(file => Copy(file, location))
}
- else
- Some(new Exit(1))
- }
- }
-
- private val scalaLoaderCache = new scala.collection.mutable.HashMap[String, ClassLoader]
- def clearScalaLoaderCache { scalaLoaderCache.clear() }
- def launcher(directory: File, mainClassName: String): Launcher = new Launch(directory, mainClassName)
- def getScalaLoader(scalaVersion: String) = scalaLoaderCache.getOrElseUpdate(scalaVersion, createScalaLoader(scalaVersion))
-
- def createScalaLoader(scalaVersion: String): ClassLoader =
- {
- val baseDirectory = new File(BootDirectory, baseDirectoryName(scalaVersion))
- val scalaDirectory = new File(baseDirectory, ScalaDirectoryName)
- val scalaLoader = newScalaLoader(scalaDirectory)
- if(needsUpdate(scalaLoader, TestLoadScalaClasses))
- {
- (new Update(baseDirectory, "", scalaVersion))(UpdateScala)
- val scalaLoader = newScalaLoader(scalaDirectory)
- failIfMissing(scalaLoader, TestLoadScalaClasses, "Scala " + scalaVersion)
- scalaLoader
- }
- else
- scalaLoader
- }
- def createSbtLoader(sbtVersion: String, scalaVersion: String): ClassLoader = createSbtLoader(sbtVersion, scalaVersion, getScalaLoader(scalaVersion))
- def createSbtLoader(sbtVersion: String, scalaVersion: String, parentLoader: ClassLoader): ClassLoader =
- {
- val baseDirectory = new File(BootDirectory, baseDirectoryName(scalaVersion))
- val mainComponentLocation = componentLocation(sbtVersion, MainSbtComponentID, scalaVersion)
- val nonComponentLocation = getSbtHome(sbtVersion, scalaVersion)
- val directories = Seq(mainComponentLocation, nonComponentLocation)
- val sbtLoader = newSbtLoader(directories, parentLoader)
- if(needsUpdate(sbtLoader, TestLoadSbtClasses))
- {
- (new Update(baseDirectory, sbtVersion, scalaVersion))(UpdateSbt)
- val sbtLoader = newSbtLoader(directories, parentLoader)
- failIfMissing(sbtLoader, TestLoadSbtClasses, "sbt " + sbtVersion)
- sbtLoader
}
- else
- sbtLoader
}
- private def newScalaLoader(dir: File) = newLoader(Seq(dir), new BootFilteredLoader(getClass.getClassLoader))
- private def newSbtLoader(directories: Seq[File], parentLoader: ClassLoader) = newLoader(directories, parentLoader)
-}
-private object Launch
-{
- def apply(args: Array[String]) = (new Launch).boot(args)
- def isYes(so: Option[String]) = isValue("y", "yes")(so)
- def isScratch(so: Option[String]) = isValue("s", "scratch")(so)
- def isValue(values: String*)(so: Option[String]) =
- so match
- {
- case Some(s) => values.contains(s.toLowerCase)
- case None => false
- }
- private def failIfMissing(loader: ClassLoader, classes: Iterable[String], label: String) =
- checkTarget(loader, classes, (), throw new BootException("Could not retrieve " + label + "."))
- private def needsUpdate(loader: ClassLoader, classes: Iterable[String]) = checkTarget(loader, classes, false, true)
- private def checkTarget[T](loader: ClassLoader, classes: Iterable[String], ifSuccess: => T, ifFailure: => T): T =
- {
- try
- {
- classes.foreach { c => Class.forName(c, false, loader) }
- ifSuccess
- }
- catch { case e: ClassNotFoundException => ifFailure }
- }
- private def newLoader(directories: Seq[File], parent: ClassLoader) = new URLClassLoader(getJars(directories), parent)
- private def getJars(directories: Seq[File]): Array[URL] = directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter))).map(_.toURI.toURL).toArray
- private def wrapNull(a: Array[File]): Array[File] = if(a == null) Array() else a
-}
-private object JarFilter extends FileFilter
-{
- def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar")
-}
-final class Exit(val code: Int) extends IExit
-
-// 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.
-private class BootException(override val toString: String) extends RuntimeException
+}
View
91 launch/LaunchConfiguration.scala
@@ -0,0 +1,91 @@
+package xsbt.boot
+
+import java.io.File
+
+final case class LaunchConfiguration(scalaVersion: Version, app: Application, repositories: Seq[Repository], boot: BootSetup, logging: Logging) extends NotNull
+{
+ def getScalaVersion = Version.get(scalaVersion)
+ def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(Version.Explicit(newScalaVersion), app, repositories, boot, logging)
+ def withApp(app: Application) = LaunchConfiguration(scalaVersion, app, repositories, boot, logging)
+ def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging)
+ def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(Version.Explicit(newScalaVersion), app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging)
+}
+
+sealed trait Version extends NotNull
+object Version
+{
+ final case class Explicit(value: String) extends Version
+ final case class Implicit(tpe: Implicit.Value, default: Option[String]) extends Version
+ {
+ require(default.isEmpty || !default.get.isEmpty, "Default cannot be empty")
+ }
+
+ object Implicit extends RichEnum
+ {
+ val Read = Value("read")
+ val Prompt = Value("prompt")
+ val ReadOrPrompt = Value("read-or-prompt")
+ def apply(s: String, default: Option[String]): Either[String, Implicit] = fromString(s).right.map(t =>Implicit(t, default))
+ }
+ def get(v: Version) = v match { case Version.Explicit(v) => v; case _ => throw new BootException("Unresolved version: " + v) }
+ def default = Implicit(Implicit.ReadOrPrompt, None)
+}
+
+trait RichEnum extends Enumeration
+{
+ def fromString(s: String): Either[String, Value] = valueOf(s).toRight("Expected one of " + elements.mkString(","))
+}
+
+final case class Application(groupID: String, name: String, version: Version, main: String, components: Seq[String], crossVersioned: Boolean) extends NotNull
+{
+ def getVersion = Version.get(version)
+ def withVersion(newVersion: Version) = Application(groupID, name, newVersion, main, components, crossVersioned)
+ def toID = AppID(groupID, name, getVersion, main, components.toArray, crossVersioned)
+}
+case class AppID(groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersioned: Boolean) extends xsbti.ApplicationID
+
+object Application
+{
+ def apply(id: xsbti.ApplicationID): Application =
+ {
+ import id._
+ Application(groupID, name, Version.Explicit(version), mainClass, mainComponents, crossVersioned)
+ }
+}
+
+sealed trait Repository extends NotNull
+object Repository
+{
+ final case class Maven(id: String, url: String) extends Repository
+ final case class Ivy(id: String, url: String, pattern: String) extends Repository
+ final case class Predefined(id: Predefined.Value) extends Repository
+
+ object Predefined extends RichEnum
+ {
+ val Local = Value("local")
+ val MavenLocal = Value("maven-local")
+ val MavenCentral = Value("maven-central")
+ val ScalaToolsReleases = Value("scala-tools-releases")
+ val ScalaToolsSnapshots = Value("scala-tools-snapshot")
+ def apply(s: String): Either[String, Predefined] = fromString(s).right.map(t => Predefined(t))
+ }
+
+ def defaults: Seq[Repository] = Predefined.elements.map(Predefined.apply).toList
+}
+
+final case class BootSetup(directory: File, properties: File) extends NotNull
+final case class Logging(level: LogLevel.Value) extends NotNull
+object LogLevel extends RichEnum
+{
+ val Debug = Value("debug")
+ val Info = Value("info")
+ val Warn = Value("warn")
+ val Error = Value("error")
+ def apply(s: String): Either[String, Logging] = fromString(s).right.map(t => Logging(t))
+}
+
+class AppConfiguration(val arguments: Array[String], val baseDirectory: File, val provider: xsbti.AppProvider) extends xsbti.AppConfiguration
+// 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
View
147 launch/ProjectProperties.scala
@@ -1,147 +0,0 @@
-/* sbt -- Simple Build Tool
- * Copyright 2009 Mark Harrah
- */
-package xsbt.boot
-
-/*
-Project does not exist, create new project? [y/N/s] y
-Name:
-Organization []:
-Version [1.0]:
-Scala version [2.7.5]:
-sbt version [0.7]:
-*/
-import java.io.File
-/** Constants related to reading/writing the build.properties file in a project.
-* See BootConfiguration for general constants used by the loader. */
-private object ProjectProperties
-{
- /** The properties key for storing the name of the project.*/
- val NameKey = "project.name"
- /** The properties key for storing the organization of the project.*/
- val OrganizationKey = "project.organization"
- /** The properties key for storing the version of the project.*/
- val VersionKey = "project.version"
- /** The properties key for storing the version of Scala used with the project.*/
- val ScalaVersionKey = "scala.version"
- /** The properties key for storing the version of sbt used to build the project.*/
- val SbtVersionKey = "sbt.version"
- /** The properties key to communicate to the main component of sbt that the project
- * should be initialized after being loaded, typically by creating a default directory structure.*/
- val InitializeProjectKey = "project.initialize"
- /** The properties key that configures the project to be flattened a bit for use by quick throwaway projects.*/
- val ScratchKey = "project.scratch"
-
- /** The label used when prompting for the name of the user's project.*/
- val NameLabel = "Name"
- /** The label used when prompting for the organization of the user's project.*/
- val OrganizationLabel = "Organization"
- /** The label used when prompting for the version of the user's project.*/
- val VersionLabel = "Version"
- /** The label used when prompting for the version of Scala to use for the user's project.*/
- val ScalaVersionLabel = "Scala version"
- /** The label used when prompting for the version of sbt to use for the user's project.*/
- val SbtVersionLabel = "sbt version"
-
- /** The default organization of the new user project when the user doesn't explicitly specify one when prompted.*/
- val DefaultOrganization = ""
- /** The default version of the new user project when the user doesn't explicitly specify a version when prompted.*/
- val DefaultVersion = "1.0"
- /** The default version of sbt when the user doesn't explicitly specify a version when prompted.*/
- val DefaultSbtVersion = "0.7"
- /** The default version of Scala when the user doesn't explicitly specify a version when prompted.*/
- val DefaultScalaVersion = "2.7.5"
-
- // sets up the project properties for a throwaway project (flattens src and lib to the root project directory)
- def scratch(file: File)
- {
- withProperties(file) { properties =>
- for( (key, _, default, _) <- propertyDefinitions(false))
- properties(key) = default.getOrElse("scratch")
- properties(ScratchKey) = true.toString
- }
- }
- // returns (scala version, sbt version)
- def apply(file: File, setInitializeProject: Boolean): (String, String) = applyImpl(file, setInitializeProject, Nil)
- def forcePrompt(file: File, propertyKeys: String*) = applyImpl(file, false, propertyKeys)
- private def applyImpl(file: File, setInitializeProject: Boolean, propertyKeys: Iterable[String]): (String, String) =
- {
- val organizationOptional = file.exists
- withProperties(file) { properties =>
- properties -= propertyKeys
-
- prompt(properties, organizationOptional)
- if(setInitializeProject)
- properties(InitializeProjectKey) = true.toString
- }
- }
- // (key, label, defaultValue, promptRequired)
- private def propertyDefinitions(organizationOptional: Boolean) =
- (NameKey, NameLabel, None, true) ::
- (OrganizationKey, OrganizationLabel, Some(DefaultOrganization), !organizationOptional) ::
- (VersionKey, VersionLabel, Some(DefaultVersion), true) ::
- (ScalaVersionKey, ScalaVersionLabel, Some(DefaultScalaVersion), true) ::
- (SbtVersionKey, SbtVersionLabel, Some(DefaultSbtVersion), true) ::
- Nil
- private def prompt(fill: ProjectProperties, organizationOptional: Boolean)
- {
- for( (key, label, default, promptRequired) <- propertyDefinitions(organizationOptional))
- {
- val value = fill(key)
- if(value == null && promptRequired)
- fill(key) = readLine(label, default)
- }
- }
- private def withProperties(file: File)(f: ProjectProperties => Unit) =
- {
- val properties = new ProjectProperties(file)
- f(properties)
- properties.save
- (properties(ScalaVersionKey), properties(SbtVersionKey))
- }
- private def readLine(label: String, default: Option[String]): String =
- {
- val prompt =
- default match
- {
- case Some(d) => "%s [%s]: ".format(label, d)
- case None => "%s: ".format(label)
- }
- SimpleReader.readLine(prompt) orElse default match
- {
- case Some(line) => line
- case None => throw new BootException("Project not loaded: " + label + " not specified.")
- }
- }
-}
-
-import java.io.{FileInputStream, FileOutputStream}
-import java.util.Properties
-private class ProjectProperties(file: File) extends NotNull
-{
- private[this] var modified = false
- private[this] val properties = new Properties
- if(file.exists)
- {
- val in = new FileInputStream(file)
- try { properties.load(in) } finally { in.close() }
- }
-
- def update(key: String, value: String)
- {
- modified = true
- properties.setProperty(key, value)
- }
- def apply(key: String) = properties.getProperty(key)
- def save()
- {
- if(modified)
- {
- file.getParentFile.mkdirs()
- val out = new FileOutputStream(file)
- try { properties.store(out, "Project Properties") } finally { out.close() }
- modified = false
- }
- }
- def -= (keys: Iterable[String]) { for(key <- keys) properties.remove(key) }
-}
View
56 launch/Provider.scala
@@ -0,0 +1,56 @@
+package xsbt.boot
+
+import java.io.{File, FileFilter}
+import java.net.{URL, URLClassLoader}
+
+trait Provider extends NotNull
+{
+ def configuration: LaunchConfiguration
+ def baseDirectories: Seq[File]
+ def testLoadClasses: Seq[String]
+ def target: UpdateTarget.Value
+ def failLabel: String
+ def parentLoader: ClassLoader
+
+ val (jars, loader) =
+ {
+ val (existingJars, existingLoader) = createLoader
+ if(Check.needsUpdate(existingLoader, testLoadClasses))
+ {
+ ( new Update(configuration) )(target)
+ val (newJars, newLoader) = createLoader
+ Check.failIfMissing(newLoader, testLoadClasses, failLabel)
+ (newJars, newLoader)
+ }
+ else
+ (existingJars, existingLoader)
+ }
+ def createLoader =
+ {
+ val jars = GetJars(baseDirectories)
+ (jars, new URLClassLoader(jars.map(_.toURI.toURL).toArray, parentLoader) )
+ }
+}
+
+object GetJars
+{
+ def apply(directories: Seq[File]): Array[File] = directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter))).toArray
+ def wrapNull(a: Array[File]): Array[File] = if(a == null) Array() else a
+
+ object JarFilter extends FileFilter
+ {
+ def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar")
+ }
+}
+object Check
+{
+ def failIfMissing(loader: ClassLoader, classes: Iterable[String], label: String) =
+ checkTarget(loader, classes, (), missing => throw new BootException("Could not retrieve " + label + ": missing " + missing.mkString(", ")))
+ def needsUpdate(loader: ClassLoader, classes: Iterable[String]) = checkTarget(loader, classes, false, x => true)
+ def checkTarget[T](loader: ClassLoader, classes: Iterable[String], ifSuccess: => T, ifFailure: Iterable[String] => T): T =
+ {
+ def classMissing(c: String) = try { Class.forName(c, false, loader); false } catch { case e: ClassNotFoundException => true }
+ val missing = classes.filter(classMissing)
+ if(missing.isEmpty) ifSuccess else ifFailure(missing)
+ }
+}
View
75 launch/ResolveVersions.scala
@@ -0,0 +1,75 @@
+package xsbt.boot
+
+import java.io.{File, FileInputStream, FileOutputStream}
+import java.util.Properties
+
+object ResolvedVersion extends Enumeration
+{
+ val Explicit, Read, Prompted = Value
+}
+final case class ResolvedVersion(v: String, method: ResolvedVersion.Value) extends NotNull
+
+object ResolveVersions
+{
+ def apply(conf: LaunchConfiguration): (LaunchConfiguration, () => Unit) = (new ResolveVersions(conf))()
+ private def doNothing = () => ()
+ private def trim(s: String) = if(s eq null) None else notEmpty(s.trim)
+ private def notEmpty(s: String) = if(s.isEmpty) None else Some(s)
+ private def readProperties(propertiesFile: File) =
+ {
+ val properties = new Properties
+ if(propertiesFile.exists)
+ Using( new FileInputStream(propertiesFile) )( properties.load )
+ properties
+ }
+ private def userDeclined(label: String) = throw new BootException("No " + label +" version specified.")
+ private def promptVersion(label: String, default: Option[String]) =
+ {
+ val message = label + default.map(" [" + _ + "]").getOrElse("") + " : "
+ SimpleReader.readLine(message).flatMap(x => notEmpty(x) orElse(default)) getOrElse(userDeclined(label))
+ }
+}
+
+import ResolveVersions.{doNothing, notEmpty, promptVersion, readProperties, trim, userDeclined}
+final class ResolveVersions(conf: LaunchConfiguration) extends NotNull
+{
+ private def propertiesFile = conf.boot.properties
+ private lazy val properties = readProperties(propertiesFile)
+ def apply(): (LaunchConfiguration, () => Unit) =
+ {
+ import conf._
+ val appVersionProperty = app.name.toLowerCase.replaceAll("\\s+",".") + ".version"
+ val scalaVersion = (new Resolve("scala.version", "Scala"))(conf.scalaVersion)
+ val appVersion = (new Resolve(appVersionProperty, app.name))(app.version)
+ val prompted = Seq((scalaVersion, conf.scalaVersion), (appVersion, app.version)).exists {
+ case (resolved, original) => resolved.method == ResolvedVersion.Prompted &&
+ ( original match { case Version.Implicit(Version.Implicit.ReadOrPrompt, _) => true; case _ => false } )
+ }
+ val finish = if(!prompted) doNothing else () => Using( new FileOutputStream(propertiesFile) ) { out => properties.store(out, "") }
+ ( withVersions(scalaVersion.v, appVersion.v), finish )
+ }
+ private final class Resolve(versionProperty: String, label: String) extends NotNull
+ {
+ def noVersionInFile = throw new BootException("No " + versionProperty + " specified in " + propertiesFile)
+ def apply(v: Version): ResolvedVersion =
+ {
+ import Version.{Explicit, Implicit}
+ import Implicit.{Prompt, Read, ReadOrPrompt}
+ v match
+ {
+ case e: Explicit => ResolvedVersion(e.value, ResolvedVersion.Explicit)
+ case Implicit(Read , default) => ResolvedVersion(readVersion() orElse default getOrElse noVersionInFile, ResolvedVersion.Read )
+ case Implicit(Prompt, default) => ResolvedVersion(promptVersion(label, default), ResolvedVersion.Prompted)
+ case Implicit(ReadOrPrompt, default) => readOrPromptVersion(default)
+ }
+ }
+ def readVersion() = trim(properties.getProperty(versionProperty))
+ def readOrPromptVersion(default: Option[String]) =
+ readVersion().map(v => ResolvedVersion(v, ResolvedVersion.Read)) getOrElse
+ {
+ val prompted = promptVersion(label, default)
+ properties.setProperty(versionProperty, prompted)
+ ResolvedVersion( prompted, ResolvedVersion.Prompted)
+ }
+ }
+}
View
47 launch/SimpleReader.scala
@@ -1,26 +1,43 @@
/* sbt -- Simple Build Tool
- * Copyright 2009 Mark Harrah
+ * Copyright 2008, 2009 Mark Harrah
*/
package xsbt.boot
import jline.ConsoleReader
-object SimpleReader extends NotNull
+abstract class JLine extends NotNull
{
- protected[this] val reader =
- {
- val cr = new ConsoleReader
- cr.setBellEnabled(false)
- cr
- }
- def readLine(prompt: String) =
+ protected[this] val reader: ConsoleReader
+ def readLine(prompt: String) = JLine.withJLine { unsynchronizedReadLine(prompt) }
+ private[this] def unsynchronizedReadLine(prompt: String) =
reader.readLine(prompt) match
{
case null => None
- case x =>
- val trimmed = x.trim
- if(trimmed.isEmpty)
- None
- else
- Some(trimmed)
+ case x => Some(x.trim)
+ }
+}
+private object JLine
+{
+ def terminal = jline.Terminal.getTerminal
+ def createReader() =
+ terminal.synchronized
+ {
+ val cr = new ConsoleReader
+ terminal.enableEcho()
+ cr.setBellEnabled(false)
+ cr
}
+ def withJLine[T](action: => T): T =
+ {
+ val t = terminal
+ t.synchronized
+ {
+ t.disableEcho()
+ try { action }
+ finally { t.enableEcho() }
+ }
+ }
+}
+object SimpleReader extends JLine
+{
+ protected[this] val reader = JLine.createReader()
}
View
137 launch/Update.scala
@@ -10,7 +10,7 @@ import core.LogOptions
import core.cache.DefaultRepositoryCacheManager
import core.event.EventManager
import core.module.id.ModuleRevisionId
-import core.module.descriptor.{Configuration, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor}
+import core.module.descriptor.{Configuration => IvyConfiguration, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor}
import core.report.ResolveReport
import core.resolve.{ResolveEngine, ResolveOptions}
import core.retrieve.{RetrieveEngine, RetrieveOptions}
@@ -21,30 +21,38 @@ import util.{DefaultMessageLogger, Message}
import BootConfiguration._
-private object UpdateTarget extends Enumeration
+object UpdateTarget extends Enumeration
{
- val UpdateScala, UpdateSbt = Value
+ val UpdateScala = Value("scala")
+ val UpdateApp = Value("app")
}
-import UpdateTarget.{UpdateSbt, UpdateScala}
+import UpdateTarget.{UpdateApp, UpdateScala}
-/** Ensures that the Scala and sbt jars exist for the given versions or else downloads them.*/
-final class Update(bootDirectory: File, sbtVersion: String, scalaVersion: String)
+/** Ensures that the Scala and application jars exist for the given versions or else downloads them.*/
+final class Update(config: LaunchConfiguration)
{
- require(!scalaVersion.trim.isEmpty, "No Scala version specified")
+ private val bootDirectory = config.boot.directory
+ bootDirectory.mkdirs
+ private val scalaVersion = config.getScalaVersion
+
private def logFile = new File(bootDirectory, UpdateLogName)
- /** A Writer to use to write the full logging information to a file for debugging. **/
- private lazy val logWriter =
+ private val logWriter = new PrintWriter(new FileWriter(logFile))
+
+ private lazy val settings =
{
- bootDirectory.mkdirs
- new PrintWriter(new FileWriter(logFile))
+ val settings = new IvySettings
+ addResolvers(settings)
+ settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
+ settings.setBaseDir(bootDirectory)
+ settings
}
+ private lazy val ivy = Ivy.newInstance(settings)
/** The main entry point of this class for use by the Update module. It runs Ivy */
def apply(target: UpdateTarget.Value)
{
- if(target == UpdateTarget.UpdateSbt)
- require(!sbtVersion.isEmpty, "No sbt version specified")
Message.setDefaultLogger(new SbtIvyLogger(logWriter))
+ ivy.pushContext()
try { update(target) }
catch
{
@@ -53,56 +61,49 @@ final class Update(bootDirectory: File, sbtVersion: String, scalaVersion: String
log(e.toString)
println(" (see " + logFile + " for complete log)")
}
- finally { logWriter.close() }
+ finally
+ {
+ logWriter.close()
+ ivy.popContext()
+ }
}
- /** Runs update for the specified target (updates either the scala or sbt jars for building the project) */
+ /** Runs update for the specified target (updates either the scala or appliciation jars for building the project) */
private def update(target: UpdateTarget.Value)
{
- val settings = new IvySettings
- val ivy = Ivy.newInstance(settings)
- ivy.pushContext()
- try { update(target, settings) }
- finally { ivy.popContext() }
- }
- private def update(target: UpdateTarget.Value, settings: IvySettings)
- {
- import Configuration.Visibility.PUBLIC
+ import IvyConfiguration.Visibility.PUBLIC
// the actual module id here is not that important
- val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot", "1.0"), "release", null, false)
+ val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot-" + target, "1.0"), "release", null, false)
moduleID.setLastModified(System.currentTimeMillis)
- moduleID.addConfiguration(new Configuration(DefaultIvyConfiguration, PUBLIC, "", Array(), true, null))
+ moduleID.addConfiguration(new IvyConfiguration(DefaultIvyConfiguration, PUBLIC, "", Array(), true, null))
// add dependencies based on which target needs updating
target match
{
case UpdateScala =>
- addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion)
- addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion)
- update(settings, moduleID, target)
- case UpdateSbt =>
- addDependency(moduleID, SbtOrg, SbtModuleName + "_" + scalaVersion, sbtVersion)
- update(settings, moduleID, target)
+ addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default")
+ addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default")
+ case UpdateApp =>
+ val resolvedName = if(config.app.crossVersioned) config.app.name + "_" + scalaVersion else config.app.name
+ addDependency(moduleID, config.app.groupID, resolvedName, config.app.getVersion, "runtime(default)")
}
+ update(moduleID, target)
}
/** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */
- private def update(settings: IvySettings, moduleID: DefaultModuleDescriptor, target: UpdateTarget.Value)
+ private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget.Value)
{
val eventManager = new EventManager
- addResolvers(settings, scalaVersion, target)
- settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
- settings.setBaseDir(bootDirectory)
- resolve(settings, eventManager, moduleID)
- retrieve(settings, eventManager, moduleID, target)
+ resolve(eventManager, moduleID)
+ retrieve(eventManager, moduleID, target)
}
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)
+ private def addDependency(moduleID: DefaultModuleDescriptor, organization: String, name: String, revision: String, conf: String)
{
val dep = new DefaultDependencyDescriptor(moduleID, createID(organization, name, revision), false, false, true)
- dep.addDependencyConfiguration(DefaultIvyConfiguration, "default")
+ dep.addDependencyConfiguration(DefaultIvyConfiguration, conf)
moduleID.addDependency(dep)
}
- private def resolve(settings: IvySettings, eventManager: EventManager, module: ModuleDescriptor)
+ private def resolve(eventManager: EventManager, module: ModuleDescriptor)
{
val resolveOptions = new ResolveOptions
// this reduces the substantial logging done by Ivy, including the progress dots when downloading artifacts
@@ -127,39 +128,45 @@ final class Update(bootDirectory: File, sbtVersion: String, scalaVersion: String
}
}
/** Retrieves resolved dependencies using the given target to determine the location to retrieve to. */
- private def retrieve(settings: IvySettings, eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget.Value)
+ private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget.Value)
{
val retrieveOptions = new RetrieveOptions
val retrieveEngine = new RetrieveEngine(settings, eventManager)
val pattern =
target match
{
- // see BuildConfiguration
- case UpdateSbt => sbtRetrievePattern(sbtVersion)
+ // see BootConfiguration
+ case UpdateApp => appRetrievePattern(config.app.toID)
case UpdateScala => scalaRetrievePattern
}
- retrieveEngine.retrieve(module.getModuleRevisionId, pattern, retrieveOptions);
+ retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaVersion) + "/" + pattern, retrieveOptions)
}
/** Add the scala tools repositories and a URL resolver to download sbt from the Google code project.*/
- private def addResolvers(settings: IvySettings, scalaVersion: String, target: UpdateTarget.Value)
+ private def addResolvers(settings: IvySettings)
{
val newDefault = new ChainResolver
newDefault.setName("redefined-public")
- newDefault.add(localResolver(settings.getDefaultIvyUserDir.getAbsolutePath))
- newDefault.add(mavenLocal)
- newDefault.add(mavenMainResolver)
- target match
- {
- case UpdateSbt =>
- newDefault.add(sbtResolver(scalaVersion))
- case UpdateScala =>
- newDefault.add(mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases"))
- newDefault.add(mavenResolver("Scala-Tools Maven2 Snapshots Repository", "http://scala-tools.org/repo-snapshots"))
- }
+ if(config.repositories.isEmpty) throw new BootException("No repositories defined.")
+ config.repositories.foreach(repo => newDefault.add(toIvyRepository(settings, repo)))
onDefaultRepositoryCacheManager(settings)(_.setUseOrigin(true))
settings.addResolver(newDefault)
settings.setDefaultResolver(newDefault.getName)
}
+ private def toIvyRepository(settings: IvySettings, repo: Repository) =
+ {
+ import Repository.{Ivy, Maven, Predefined}
+ import Predefined._
+ repo match
+ {
+ case Maven(id, url) => mavenResolver(id, url)
+ case Ivy(id, url, pattern) => urlResolver(id, url, pattern)
+ case Predefined(Local) => localResolver(settings.getDefaultIvyUserDir.getAbsolutePath)
+ case Predefined(MavenLocal) => mavenLocal
+ case Predefined(MavenCentral) => mavenMainResolver
+ case Predefined(ScalaToolsReleases) => mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases")
+ case Predefined(ScalaToolsSnapshots) => mavenResolver("Scala-Tools Maven2 Snapshots Repository", "http://scala-tools.org/repo-snapshots")
+ }
+ }
private def onDefaultRepositoryCacheManager(settings: IvySettings)(f: DefaultRepositoryCacheManager => Unit)
{
settings.getDefaultRepositoryCacheManager match
@@ -169,13 +176,13 @@ final class Update(bootDirectory: File, sbtVersion: String, scalaVersion: String
}
}
/** Uses the pattern defined in BuildConfiguration to download sbt from Google code.*/
- private def sbtResolver(scalaVersion: String) =
+ private def urlResolver(id: String, base: String, pattern: String) =
{
- val pattern = sbtResolverPattern(scalaVersion)
val resolver = new URLResolver
- resolver.setName("Sbt Repository")
- resolver.addIvyPattern(pattern)
- resolver.addArtifactPattern(pattern)
+ resolver.setName(id)
+ val adjusted = (if(base.endsWith("/")) base else (base + "/") ) + pattern
+ resolver.addIvyPattern(adjusted)
+ resolver.addArtifactPattern(adjusted)
resolver
}
private def mavenLocal = mavenResolver("Maven2 Local", "file://" + System.getProperty("user.home") + "/.m2/repository/")
@@ -199,12 +206,10 @@ final class Update(bootDirectory: File, sbtVersion: String, scalaVersion: String
private def localResolver(ivyUserDirectory: String) =
{
val localIvyRoot = ivyUserDirectory + "/local"
- val artifactPattern = localIvyRoot + "/" + LocalArtifactPattern
- val ivyPattern = localIvyRoot + "/" + LocalIvyPattern
val resolver = new FileSystemResolver
resolver.setName(LocalIvyName)
- resolver.addIvyPattern(ivyPattern)
- resolver.addArtifactPattern(artifactPattern)
+ resolver.addIvyPattern(localIvyRoot + "/" + LocalIvyPattern)
+ resolver.addArtifactPattern(localIvyRoot + "/" + LocalArtifactPattern)
resolver
}
/** Logs the given message to a file and to the console. */
View
40 launch/Using.scala
@@ -0,0 +1,40 @@
+package xsbt.boot
+
+import java.io.{Closeable, File, FileInputStream, FileOutputStream, InputStream, OutputStream}
+
+object Using extends NotNull
+{
+ 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: Array[File], to: File)
+ {
+ to.mkdirs()
+ files.foreach(file => apply(file, to))
+ }
+ def apply(file: File, to: File)
+ {
+ Using(new FileInputStream(file)) { in =>
+ Using(new FileOutputStream(new File(to, file.getName))) { out =>
+ transfer(in, out)
+ }
+ }
+ }
+ def transfer(in: InputStream, out: OutputStream)
+ {
+ val buffer = new Array[Byte](8192)
+ def next()
+ {
+ val read = in.read(buffer)
+ if(read > 0)
+ {
+ out.write(buffer, 0, read)
+ next()
+ }
+ }
+ next()
+ }
+}
View
10 launch/interface/src/main/java/xsbti/AppConfiguration.java
@@ -0,0 +1,10 @@
+package xsbti;
+
+import java.io.File;
+
+public interface AppConfiguration
+{
+ public String[] arguments();
+ public File baseDirectory();
+ public AppProvider provider();
+}
View
6 launch/interface/src/main/java/xsbti/AppMain.java
@@ -0,0 +1,6 @@
+package xsbti;
+
+public interface AppMain
+{
+ public MainResult run(AppConfiguration configuration);
+}
View
15 launch/interface/src/main/java/xsbti/AppProvider.java
@@ -0,0 +1,15 @@
+package xsbti;
+
+import java.io.File;
+
+public interface AppProvider
+{
+ public ScalaProvider scalaProvider();
+ public ApplicationID id();
+
+ public Class<? extends AppMain> mainClass();
+ public AppMain newMain();
+
+ public File[] component(String componentID);
+ public void defineComponent(String componentID, File[] components);
+}
View
12 launch/interface/src/main/java/xsbti/ApplicationID.java
@@ -0,0 +1,12 @@
+package xsbti;
+
+public interface ApplicationID
+{
+ public String groupID();
+ public String name();
+ public String version();
+
+ public String mainClass();
+ public String[] mainComponents();
+ public boolean crossVersioned();
+}
View
19 launch/interface/src/main/java/xsbti/Launcher.java
@@ -1,22 +1,7 @@
package xsbti;
-import java.io.File;
-
-public interface Launcher extends ScalaProvider, SbtProvider
+public interface Launcher
{
public static final int InterfaceVersion = 1;
-
- public void boot(String[] args);
- public MainResult checkAndLoad(String[] args);
- public MainResult load(String[] args);
- public MainResult load(String[] args, String useSbtVersion, String mainClassName, String definitionScalaVersion);
- public ClassLoader update(String scalaVersion, String sbtVersion);
- public MainResult run(ClassLoader sbtLoader, String mainClassName, SbtConfiguration configuration);
-
- public File BaseDirectory();
- public File ProjectDirectory();
- public File BootDirectory();
- public File PropertiesFile();
-
- public Launcher launcher(File base, String mainClassName);
+ public ScalaProvider getScala(String version);
}
View
6 launch/interface/src/main/java/xsbti/Reboot.java
@@ -1,5 +1,11 @@
package xsbti;
+
+import java.io.File;
+
public interface Reboot extends MainResult
{
public String[] arguments();
+ public File baseDirectory();
+ public String scalaVersion();
+ public ApplicationID app();
}
View
10 launch/interface/src/main/java/xsbti/SbtConfiguration.java
@@ -1,10 +0,0 @@
-package xsbti;
-
-public interface SbtConfiguration
-{
- public String[] arguments();
- public String scalaVersion();
- public String sbtVersion();
- public java.io.File baseDirectory();
- public Launcher launcher();
-}
View
6 launch/interface/src/main/java/xsbti/SbtMain.java
@@ -1,6 +0,0 @@
-package xsbti;
-
-public interface SbtMain
-{
- public MainResult run(SbtConfiguration configuration);
-}
View
12 launch/interface/src/main/java/xsbti/SbtProvider.java
@@ -1,12 +0,0 @@
-package xsbti;
-
-import java.io.File;
-
-public interface SbtProvider
-{
- public ClassLoader createSbtLoader(String sbtVersion, String scalaVersion);
- public ClassLoader createSbtLoader(String sbtVersion, String scalaVersion, ClassLoader parentLoader);
-
- public File getSbtHome(String sbtVersion, String scalaVersion);
- public File componentLocation(String sbtVersion, String id, String scalaVersion);
-}
View
8 launch/interface/src/main/java/xsbti/ScalaProvider.java
@@ -4,6 +4,10 @@
public interface ScalaProvider
{
- public ClassLoader getScalaLoader(String scalaVersion);
- public File getScalaHome(String scalaVersion);
+ public Launcher launcher();
+ public String version();
+
+ public ClassLoader loader();
+ public File[] jars();
+ public AppProvider app(ApplicationID id);
}
View
25 launch/src/main/resources/sbt/sbt.boot.properties
@@ -0,0 +1,25 @@
+[scala]
+ version: read-or-prompt, 2.7.5
+
+[app]
+ org: org.scala-tools.sbt
+ name: xsbt
+ version: read-or-prompt, 0.7.0_13
+ class: xsbt.Main
+ components: xsbti
+ cross-versioned: true
+
+[repositories]
+ local
+ maven-local
+# Sbt Repository, http://simple-build-tool.googlecode.com/svn/artifacts/, [revision]/[type]s/[artifact].[ext]
+ maven-central
+ scala-tools-releases
+# scala-tools-snapshots
+
+[boot]
+ directory: project/boot
+ properties: project/build.properties
+
+[log]
+ level: info
View
22 notes
@@ -30,14 +30,14 @@ Dependency Management
- resolvers are completely defined in project definition (use Resolver.withDefaultResolvers)
- configurations completely defined within project (use ModuleConfiguration.configurations)
-
-TODO:
-compiler analysis plugin does not check classes against output directory. This must now be done in the callback itself. The old code was:
-
- Path.relativize(outputPath, pf.file) match
- {
- case None => // dependency is a class file outside of the output directory
- callback.classDependency(pf.file, sourcePath)
- case Some(relativeToOutput) => // dependency is a product of a source not included in this compilation
- callback.productDependency(relativeToOutput, sourcePath)
- }
+Launcher
+- generalize to be able to launch any conforming application
+- default configuration file in jar- can be replaced in jar or overridden on command line
+- format:
+ scala.version=versionSpecification
+ main.org=ID
+ main.name=ID
+ main.version=versionSpecification
+ main.components=ID (',' ID)*
+ repo=id ',' url [',' pattern]
+ versionSpecification := ( ('read'|'prompt'|'readOrPrompt') [default] ) |version
View
4 project/build.properties
@@ -1,7 +1,7 @@
#Project properties
-#Tue Sep 08 15:55:04 EDT 2009
+#Sun Sep 13 00:00:33 EDT 2009
project.organization=org.scala-tools.sbt
project.name=xsbt
-sbt.version=0.5.3-p4
+sbt.version=0.5.4
project.version=0.7.0_13
scala.version=2.7.5
View
2  project/build/ProguardProject.scala
@@ -60,7 +60,7 @@ trait ProguardProject extends BasicScalaProject
FileUtilities.clean(outputJar :: Nil, log)
val proguardClasspathString = Path.makeString(managedClasspath(toolsConfig).get)
val configFile = proguardConfigurationPath.asFile.getAbsolutePath
- val exitValue = Process("java", List("-Xmx128M", "-cp", proguardClasspathString, "proguard.ProGuard", "@" + configFile)) ! log
+ val exitValue = Process("java", List("-Xmx256M", "-cp", proguardClasspathString, "proguard.ProGuard", "@" + configFile)) ! log
if(exitValue == 0) None else Some("Proguard failed with nonzero exit code (" + exitValue + ")")
}
private def writeProguardConfigurationTask =
View
13 project/build/XSbt.scala
@@ -109,11 +109,8 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
{
val ivy = "org.apache.ivy" % "ivy" % "2.0.0"
}
- class InterfaceProject(info: ProjectInfo) extends DefaultProject(info) with ManagedBase with TestWithLog with Component
+ class InterfaceProject(info: ProjectInfo) extends DefaultProject(info) with ManagedBase with TestWithLog with Component with JavaProject
{
- // ensure that interfaces are only Java sources and that they cannot reference Scala classes
- override def mainSources = descendents(mainSourceRoots, "*.java")
- override def compileOrder = CompileOrder.JavaThenScala
override def componentID: Option[String] = Some("xsbti")
}
class LaunchInterfaceProject(info: ProjectInfo) extends InterfaceProject(info)
@@ -137,7 +134,13 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
override def testClasspath = super.testClasspath +++ logSub.compileClasspath
}
}
-
+trait JavaProject extends BasicScalaProject
+{
+ override final def crossScalaVersions = Set.empty // don't need to cross-build Java sources
+ // ensure that interfaces are only Java sources and that they cannot reference Scala classes
+ override def mainSources = descendents(mainSourceRoots, "*.java")
+ override def compileOrder = CompileOrder.JavaThenScala
+}
trait SourceProject extends BasicScalaProject
{
override final def crossScalaVersions = Set.empty // don't need to cross-build a source package
View
2  tasks/src/test/scala/TestRunnerSort.scala
@@ -36,7 +36,7 @@ object TaskRunnerSortTest extends Properties("TaskRunnerSort")
Task(a) bind { a =>
val pivot = a(0)
val (lt,gte) = a.projection.drop(1).partition(_ < pivot)
- (sort(lt), sort(gte)) map { (l,g) => l ++ List(pivot) ++ g }
+ (sort(lt), sort(gte)) map { _ ++ List(pivot) ++ _ }
}
}
}

0 comments on commit 56e96c3

Please sign in to comment.
Something went wrong with that request. Please try again.