Permalink
Browse files

New generalized launcher

  • Loading branch information...
1 parent 76e8140 commit 56e96c3f49ece6b4255e52385069c45376753c54 @harrah committed Sep 26, 2009
View
@@ -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
@@ -3,7 +3,6 @@
*/
package xsbt.boot
-import BootConfiguration.{SbtMainClass, SbtModuleName}
import java.io.File
// The entry point to the launcher
@@ -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
@@ -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
+ }
+}
Oops, something went wrong.

0 comments on commit 56e96c3

Please sign in to comment.