Permalink
Browse files

Update launch specification, fix launch initialization, general cleanup

  • Loading branch information...
harrah committed Oct 15, 2009
1 parent 51d8d24 commit 1c9cabc69ff27f37a1f185915f504ebb4c2fa84a
View
@@ -5,25 +5,26 @@ The sbt launcher component is a self-contained jar that boots a Scala applicatio
== 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.
+ * Replace the /sbt/sbt.boot.properties file in the jar
+ * Put a configuration file named sbt.boot.properties on the classpath. Put it in the classpath root without the /sbt prefix.
* Specify the location of an alternate configuration on the command line. This can be done by either specifying the location as the system property 'sbt.boot.properties' or as the first argument to the launcher prefixed by '@'. The system property has lower precedence. 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
+configuration ::= scala app repositories boot log app-properties
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 search nl
- log ::= '[' log ']' nl logLevel nl
+ log ::= '[' 'log' ']' nl logLevel nl
+ app-properties ::= '[' 'app-properties' ']' nl property*
directory ::= 'directory' ':' path
properties ::= 'properties' ':' path
search ::= 'search' ':' ('none'|'nearest'|'root-first'|'only') (',' path)*
logLevel ::= 'log-level' ':' ('debug' | 'info' | 'warn' | 'error')
- versionSpecification ::= 'version' ':' ( ( ('read'|'prompt'|'read-or-prompt') [defaultVersion] ) | fixedVersion )
+ versionSpecification ::= 'version' ':' ( ( 'read' [defaultVersion] ) | fixedVersion )
defaultVersion ::= text
fixedVersion ::= text
@@ -36,42 +37,56 @@ configuration ::= scala app repositories boot log
repository ::= ( predefinedRepository | ( label ':' url [',' pattern] ) ) nl
predefinedRepository ::= 'local' | 'maven-local' | 'maven-central' | 'scala-tools-releases' | 'scala-tools-snapshots'
+ property ::= label ':' propertyDefinition (',' propertyDefinition)* nl
+ propertyDefinition ::= ('quick' | 'new' | 'fill') '=' (set | prompt)
+ set ::= 'set' '(' text ')'
+ prompt ::= 'prompt' '(' text ')' ('[' text ']')?
+
The default configuration file for sbt looks like:
[scala]
- version: read-or-prompt, 2.7.5
+ version: read
[app]
org: org.scala-tools.sbt
- name: xsbt
- version: read-or-prompt, 0.7.0_13
- class: xsbt.Main
- components: xsbti, default
+ name: sbt
+ version: read
+ class: sbt.xMain
+ 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
- search: none
+ prompt-create: Project does not exist, create new project?
+ prompt-fill: true
+ quick-option: true
[log]
level: info
+[app-properties]
+ project.name: quick=set(test), new=prompt(Name), fill=prompt(Name)
+ project.version: quick=set(1.0), new=prompt(Version)[1.0], fill=prompt(Version)[1.0]
+ build.init.scala.version: quick=set(2.7.5), new=prompt(Scala version)[2.7.5]
+ scala.version: quick=set(2.7.5), new=set(2.7.5), fill=set(2.7.5)
+ sbt.version: quick=set(0.5.6-SNAPSHOT), new=prompt(sbt version)[0.5.6-SNAPSHOT], fill=prompt(sbt version)[0.5.6-SNAPSHOT]
+ project.scratch: quick=set(true)
+ project.initialize: quick=set(true), new=set(true)
+
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 that implements xsbti.AppMain. The AppMain interface specifies the entry method signature 'run'. The run method is passed an instance of AppConfiguration, which provides access to the startup environment. AppConfiguration also provides an interface to retrieve other versions of Scala or other applications. Finally, the return type of the run method is `xsbti.MainResult`, which has two subtypes: xsbti.Reboot and xsbti.Exit. To exit with a specific code, return an instance of xsbti.Exit with the requested code. To restart the application, return an instance of Reboot. You can change some aspects of the configuration with a reboot, such as the version of Scala, the application ID, and the arguments.
== 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 the 'boot.properties' property is read as a Java properties file to obtain the version. The property names are [app.name].version for the application version (where [app.name] is replaced with the value of the app.name property from the boot configuration file) 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 file specified by 'boot.properties' is updated with the value specified by the user.
+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 (specified as 'read'), the launcher determines them in the following manner. The file given by the 'boot.properties' property is read as a Java properties file to obtain the version. The property names are [app.name].version for the application version (where [app.name] is replaced with the value of the app.name property from the boot configuration file) 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.
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]/.
@@ -136,6 +151,6 @@ Define a configuration file for the launcher. For the above class, it might loo
Then, +publish-local the application to make it available.
As mentioned above, there are a few options to actually run the application. The first involves providing a modified jar for download. The second two require providing a configuration file for download.
-1) Replace the sbt.boot.properties file in the launcher jar and distribute the modified jar. The user would need to run 'java -jar your-launcher.jar'.
+1) Replace the /sbt/sbt.boot.properties file in the launcher jar and distribute the modified jar. The user would need to run 'java -jar your-launcher.jar'.
2) The user downloads the vanilla sbt launcher jar and you provide the sbt.boot.properties file. The user would need to run 'java -Dsbt.boot.properties=your.boot.properties -jar sbt-launcher.jar'
3) The user sets up the sbt launcher, including the bash script. You provide the sbt.boot.properties file and the user runs sbt @your.boot.properties <other arguments>.
@@ -126,7 +126,7 @@ class ConfigurationParser extends Parsers with NotNull
case "set" => SetProperty(requiredArg)
case _ => throw new BootException("Unknown action '" + action + "' for property '" + name + "'")
}
- private lazy val propertyPattern = """(.+)\((.*)\)(?:\[(.*)\])?""".r.pattern
+ private lazy val propertyPattern = """(.+)\((.*)\)(?:\[(.*)\])?""".r.pattern // 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)
View
@@ -13,7 +13,7 @@ object Initialize
case Some(line) =>
line.toLowerCase match
{
- case "y" | "yes" => file.createNewFile(); process(file, spec, _.create)
+ case "y" | "yes" => process(file, spec, _.create)
case "n" | "no" | "" => throw new BootException("")
case "s" => process(file, spec, _.quick)
}
@@ -27,6 +27,7 @@ object Initialize
Using(new FileInputStream(file))( properties.load )
for(property <- appProperties; init <- select(property) if properties.getProperty(property.name) == null)
initialize(properties, property.name, init)
+ file.getParentFile.mkdirs()
Using(new FileOutputStream(file))( out => properties.save(out, "") )
}
def initialize(properties: Properties, name: String, init: PropertyInit)
View
@@ -1,9 +1,7 @@
package xsbt.boot
-import java.io.{File, FileOutputStream}
+import java.io.File
import java.net.URL
-import java.nio.channels.FileChannel
-import java.util.concurrent.Callable
object Launch
{
@@ -28,11 +26,11 @@ object Launch
initialized(currentDirectory, parsed, arguments)
}
def initialized(currentDirectory: File, parsed: LaunchConfiguration, arguments: Seq[String]): Unit =
- ResolveVersions(parsed) match { case (resolved, finish) => explicit(currentDirectory, resolved, arguments, finish) }
+ explicit(currentDirectory, ResolveVersions(parsed), arguments)
- def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: Seq[String], setupComplete: () => Unit): Unit =
+ def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: Seq[String]): Unit =
launch( run(new Launch(explicit.boot.directory, explicit.repositories)) ) (
- RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments, setupComplete) )
+ RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments) )
def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult =
{
@@ -41,21 +39,19 @@ object Launch
val appProvider: xsbti.AppProvider = scalaProvider.app(app)
val appConfig: xsbti.AppConfiguration = new AppConfiguration(arguments.toArray, workingDirectory, appProvider)
- val main = appProvider.newMain()
- setupComplete()
- main.run(appConfig)
+ appProvider.newMain().run(appConfig)
}
final def launch(run: RunConfiguration => xsbti.MainResult)(config: RunConfiguration)
{
run(config) match
{
case e: xsbti.Exit => System.exit(e.code)
- case r: xsbti.Reboot => launch(run)(RunConfiguration(r.scalaVersion, r.app, r.baseDirectory, r.arguments, () => ()))
+ case r: xsbti.Reboot => launch(run)(RunConfiguration(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 + ")"))
}
}
}
-case class RunConfiguration(scalaVersion: String, app: xsbti.ApplicationID, workingDirectory: File, arguments: Seq[String], setupComplete: () => Unit) extends NotNull
+case class RunConfiguration(scalaVersion: String, app: xsbti.ApplicationID, workingDirectory: File, arguments: Seq[String]) extends NotNull
import BootConfiguration.{appDirectoryName, baseDirectoryName, ScalaDirectoryName, TestLoadScalaClasses}
class Launch(val bootDirectory: File, repositories: Seq[Repository]) extends xsbti.Launcher
@@ -128,52 +124,4 @@ object ComponentProvider
baseDirectory.mkdirs()
new File(baseDirectory, "sbt.components.lock")
}
-}
-// gets a file lock by first getting a JVM-wide lock.
-object Locks extends xsbti.GlobalLock
-{
- import scala.collection.mutable.HashMap
- private[this] val locks = new HashMap[File, GlobalLock]
- def apply[T](file: File, action: Callable[T]) =
- {
- val canonFile = file.getCanonicalFile
- synchronized { locks.getOrElseUpdate(canonFile, new GlobalLock(canonFile)).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 { withFileLock(run) }
- finally { fileLocked = false }
- }
- }
- private[this] def withFileLock[T](run: Callable[T]): T =
- {
- def withChannel(channel: FileChannel) =
- {
- val freeLock = channel.tryLock
- if(freeLock eq null)
- {
- println("Waiting for lock on " + file + " to be available...");
- val lock = channel.lock
- try { run.call }
- finally { lock.release() }
- }
- else
- {
- try { run.call }
- finally { freeLock.release() }
- }
- }
- Using(new FileOutputStream(file).getChannel)(withChannel)
- }
- }
-}
+}
View
@@ -0,0 +1,54 @@
+package xsbt.boot
+
+import java.io.{File, FileOutputStream}
+import java.nio.channels.FileChannel
+import java.util.concurrent.Callable
+
+// gets a file lock by first getting a JVM-wide lock.
+object Locks extends xsbti.GlobalLock
+{
+ import scala.collection.mutable.HashMap
+ private[this] val locks = new HashMap[File, GlobalLock]
+ def apply[T](file: File, action: Callable[T]) =
+ {
+ val canonFile = file.getCanonicalFile
+ synchronized { locks.getOrElseUpdate(canonFile, new GlobalLock(canonFile)).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 { withFileLock(run) }
+ finally { fileLocked = false }
+ }
+ }
+ private[this] def withFileLock[T](run: Callable[T]): T =
+ {
+ def withChannel(channel: FileChannel) =
+ {
+ val freeLock = channel.tryLock
+ if(freeLock eq null)
+ {
+ println("Waiting for lock on " + file + " to be available...");
+ val lock = channel.lock
+ try { run.call }
+ finally { lock.release() }
+ }
+ else
+ {
+ try { run.call }
+ finally { freeLock.release() }
+ }
+ }
+ Using(new FileOutputStream(file).getChannel)(withChannel)
+ }
+ }
+}
@@ -1,18 +1,13 @@
package xsbt.boot
-import java.io.{File, FileInputStream, FileOutputStream}
+import java.io.{File, FileInputStream}
import java.util.Properties
-object ResolvedVersion extends Enumeration
-{
- val Explicit, Read = Value
-}
-final case class ResolvedVersion(v: String, method: ResolvedVersion.Value) extends NotNull
+final case class ResolvedVersion(v: String, wasExplicit: Boolean) extends NotNull
object ResolveVersions
{
- def apply(conf: LaunchConfiguration): (LaunchConfiguration, () => Unit) = (new ResolveVersions(conf))()
- private def doNothing = () => ()
+ def apply(conf: LaunchConfiguration): LaunchConfiguration = (new ResolveVersions(conf))()
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) =
@@ -24,30 +19,28 @@ object ResolveVersions
}
}
-import ResolveVersions.{doNothing, notEmpty, readProperties, trim}
+import ResolveVersions.{readProperties, trim}
final class ResolveVersions(conf: LaunchConfiguration) extends NotNull
{
private def propertiesFile = conf.boot.properties
private lazy val properties = readProperties(propertiesFile)
- def apply(): (LaunchConfiguration, () => Unit) =
+ def apply(): LaunchConfiguration =
{
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 finish = () => Using( new FileOutputStream(propertiesFile) ) { out => properties.store(out, "") }
- ( withVersions(scalaVersion.v, appVersion.v), finish )
+ withVersions(scalaVersion, appVersion)
}
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 =
+ def apply(v: Version): String =
{
- import Version.{Explicit, Implicit}
v match
{
- case e: Explicit => ResolvedVersion(e.value, ResolvedVersion.Explicit)
- case Implicit(default) => ResolvedVersion(readVersion() orElse default getOrElse noVersionInFile, ResolvedVersion.Read )
+ case e: Version.Explicit => e.value
+ case Version.Implicit(default) => readVersion() orElse default getOrElse noVersionInFile
}
}
def readVersion() = trim(properties.getProperty(versionProperty))
@@ -20,17 +20,18 @@
[boot]
directory: project/boot
properties: project/build.properties
- prompt-create: true
+ prompt-create: Project does not exist, create new project?
prompt-fill: true
quick-option: true
[log]
level: info
[app-properties]
- project.name: quick=set[test], new=prompt, fill=prompt
- project.version: quick=set[1.0], new=prompt[1.0], fill=prompt[1.0]
- scala.version: quick=set[2.7.5], new=prompt[2.7.5], fill=prompt[2.7.5]
- sbt.version: quick=set[0.5.6-SNAPSHOT], new=prompt[0.5.6-SNAPSHOT], fill=prompt[0.5.6-SNAPSHOT]
- project.scratch: quick=set[true]
- project.initialize: quick=set[true], new=set[true]
+ project.name: quick=set(test), new=prompt(Name), fill=prompt(Name)
+ project.version: quick=set(1.0), new=prompt(Version)[1.0], fill=prompt(Version)[1.0]
+ build.init.scala.version: quick=set(2.7.5), new=prompt(Scala version)[2.7.5]
+ scala.version: quick=set(2.7.5), new=set(2.7.5), fill=set(2.7.5)
+ sbt.version: quick=set(0.5.6-SNAPSHOT), new=prompt(sbt version)[0.5.6-SNAPSHOT], fill=prompt(sbt version)[0.5.6-SNAPSHOT]
+ project.scratch: quick=set(true)
+ project.initialize: quick=set(true), new=set(true)

0 comments on commit 1c9cabc

Please sign in to comment.