diff --git a/ivy/src/main/scala/sbt/CrossVersion.scala b/ivy/src/main/scala/sbt/CrossVersion.scala index ed5533a44f..6e5df81978 100644 --- a/ivy/src/main/scala/sbt/CrossVersion.scala +++ b/ivy/src/main/scala/sbt/CrossVersion.scala @@ -1,12 +1,14 @@ package sbt +import cross.CrossVersionUtil + final case class ScalaVersion(full: String, binary: String) sealed trait CrossVersion object CrossVersion { - val TransitionScalaVersion = "2.10" - val TransitionSbtVersion = "0.12" + val TransitionScalaVersion = CrossVersionUtil.TransitionScalaVersion + val TransitionSbtVersion = CrossVersionUtil.TransitionSbtVersion object Disabled extends CrossVersion { override def toString = "disabled" } final class Binary(val remapVersion: String => String) extends CrossVersion { @@ -71,58 +73,22 @@ object CrossVersion def isStable(v: String): Boolean = isScalaApiCompatible(v) @deprecated("Use CrossVersion.scalaApiVersion or CrossVersion.sbtApiVersion", "0.13.0") def selectVersion(full: String, binary: String): String = if(isStable(full)) binary else full - def isSbtApiCompatible(v: String): Boolean = sbtApiVersion(v).isDefined + def isSbtApiCompatible(v: String): Boolean = CrossVersionUtil.isSbtApiCompatible(v) /** Returns sbt binary interface x.y API compatible with the given version string v. * RCs for x.y.0 are considered API compatible. * Compatibile versions include 0.12.0-1 and 0.12.0-RC1 for Some(0, 12). */ - def sbtApiVersion(v: String): Option[(Int, Int)] = - { - val ReleaseV = """(\d+)\.(\d+)\.(\d+)(-\d+)?""".r - val CandidateV = """(\d+)\.(\d+)\.(\d+)(-RC\d+)""".r - val NonReleaseV = """(\d+)\.(\d+)\.(\d+)(-\w+)""".r - v match { - case ReleaseV(x, y, z, ht) => Some((x.toInt, y.toInt)) - case CandidateV(x, y, z, ht) => Some((x.toInt, y.toInt)) - case NonReleaseV(x, y, z, ht) if z.toInt > 0 => Some((x.toInt, y.toInt)) - case _ => None - } - } - def isScalaApiCompatible(v: String): Boolean = scalaApiVersion(v).isDefined + def sbtApiVersion(v: String): Option[(Int, Int)] = CrossVersionUtil.sbtApiVersion(v) + def isScalaApiCompatible(v: String): Boolean = CrossVersionUtil.isScalaApiCompatible(v) /** Returns Scala binary interface x.y API compatible with the given version string v. * Compatibile versions include 2.10.0-1 and 2.10.1-M1 for Some(2, 10), but not 2.10.0-RC1. */ - def scalaApiVersion(v: String): Option[(Int, Int)] = - { - val ReleaseV = """(\d+)\.(\d+)\.(\d+)(-\d+)?""".r - val NonReleaseV = """(\d+)\.(\d+)\.(\d+)(-\w+)""".r - v match { - case ReleaseV(x, y, z, ht) => Some((x.toInt, y.toInt)) - case NonReleaseV(x, y, z, ht) if z.toInt > 0 => Some((x.toInt, y.toInt)) - case _ => None - } - } - val PartialVersion = """(\d+)\.(\d+)(?:\..+)?""".r - def partialVersion(s: String): Option[(Int,Int)] = - s match { - case PartialVersion(major, minor) => Some(major.toInt, minor.toInt) - case _ => None - } - private[this] def isNewer(major: Int, minor: Int, minMajor: Int, minMinor: Int): Boolean = - major > minMajor || (major == minMajor && minor >= minMinor) - - def binaryScalaVersion(full: String): String = binaryVersionWithApi(full, TransitionScalaVersion)(scalaApiVersion) - def binarySbtVersion(full: String): String = binaryVersionWithApi(full, TransitionSbtVersion)(sbtApiVersion) + def scalaApiVersion(v: String): Option[(Int, Int)] = CrossVersionUtil.scalaApiVersion(v) + val PartialVersion = CrossVersionUtil.PartialVersion + def partialVersion(s: String): Option[(Int,Int)] = CrossVersionUtil.partialVersion(s) + def binaryScalaVersion(full: String): String = CrossVersionUtil.binaryScalaVersion(full) + def binarySbtVersion(full: String): String = CrossVersionUtil.binarySbtVersion(full) @deprecated("Use CrossVersion.scalaApiVersion or CrossVersion.sbtApiVersion", "0.13.0") - def binaryVersion(full: String, cutoff: String): String = binaryVersionWithApi(full, cutoff)(scalaApiVersion) - private[this] def binaryVersionWithApi(full: String, cutoff: String)(apiVersion: String => Option[(Int,Int)]): String = - { - def sub(major: Int, minor: Int) = major + "." + minor - (apiVersion(full), partialVersion(cutoff)) match { - case (Some((major, minor)), None) => sub(major, minor) - case (Some((major, minor)), Some((minMajor, minMinor))) if isNewer(major, minor, minMajor, minMinor) => sub(major, minor) - case _ => full - } - } -} + def binaryVersion(full: String, cutoff: String): String = CrossVersionUtil.binaryVersion(full, cutoff) +} diff --git a/launch.specification b/launch.specification deleted file mode 100644 index e6f5247c98..0000000000 --- a/launch.specification +++ /dev/null @@ -1,218 +0,0 @@ -= 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. - -= Overview = - -A user downloads the launcher jar and creates a script to run it. In this documentation, the script will be assumed to be called 'launch'. For unix, the script would look like: -{{{ - java -jar sbt-launcher.jar "$@" -}}} - -The user then downloads the configuration file for the application (call it `my.app.configuration`) and creates a script to launch it (call it `myapp`): -{{{ - launch @my.app.configuration "$@" -}}} - -The user can then launch the application using -{{{ - myapp arg1 arg2 ... -}}} - -Like the launcher used to distribute `sbt`, the downloaded launcher jar will retrieve Scala and the application. The difference is that this behavior is now configurable. The versions may be fixed or read from a configuration file (the location of which is also configurable). The location to which the Scala and application jars are downloaded is configurable as well. The repositories searched are configurable. Optional initialization of a properties file is configurable. - -Once the launcher has downloaded the necessary jars, it loads the application and calls its entry point. The application is passed information about how it was called: command line arguments, current working directory, Scala version, and application ID (organization, name, version). In addition, the launcher can ask the launcher to perform operations such as obtaining the Scala jars and a `ClassLoader` for any version of Scala retrievable from the repositories specified in the configuration file. It can request that other applications be downloaded and run. When the application completes, it can tell the launcher to exit with a specific exit code or to reload the application with a different version of Scala, a different version of the application, or different arguments. - -There are some other options for setup, such as putting the configuration file inside the launcher jar and distributing that as a single download. The rest of this documentation describes the details of configuring, writing, distributing, and running the application. - -== Configuration == - -The launcher may be configured in one of the following ways in increasing order of precedence: - * 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 line-based, read as UTF-8 encoded, and defined by the following grammer. `'nl'` is a newline or end of file and `'text'` is plain text without newlines or the surrounding delimiters (such as parentheses or square brackets): -{{{ -configuration ::= scala app repositories boot log app-properties - scala ::= '[' 'scala' ']' nl version nl classifiers nl - app ::= '[' 'app' ']' nl org nl name nl version nl components nl class nl cross-versioned nl resources nl - repositories ::= '[' 'repositories' ']' nl (repository nl)* - boot ::= '[' 'boot' ']' nl directory nl bootProperties nl search nl promptCreate nl promptFill nl quickOption nl - log ::= '[' 'log' ']' nl logLevel nl - app-properties ::= '[' 'app-properties' ']' nl property* - - directory ::= 'directory' ':' path - bootProperties ::= 'properties' ':' path - search ::= 'search' ':' ('none'|'nearest'|'root-first'|'only') (',' path)* - logLevel ::= 'log-level' ':' ('debug' | 'info' | 'warn' | 'error') - promptCreate ::= 'prompt-create' ':' label - promptFill ::= 'prompt-fill' ':' boolean - quickOption ::= 'quick-option' ':' boolean - - version ::= 'version' ':' versionSpecification - versionSpecification ::= readProperty | fixedVersion - readProperty ::= 'read' '(' propertyName ')' '[' default ']' - fixedVersion ::= text - - classifiers ::= 'classifiers' ':' text (',' text)* - - org ::= 'org' ':' text - name ::= 'name' ':' text - class ::= 'class' ':' text - components ::= 'components' ':' component (',' component)* - cross-versioned ::= 'cross-versioned' ':' boolean - resources ::= 'resources' ':' path (',' path)* - - repository ::= ( predefinedRepository | customRepository ) nl - predefinedRepository ::= 'local' | 'maven-local' | 'maven-central' | 'scala-tools-releases' | 'scala-tools-snapshots' - customRepository ::= label ':' url [[',' pattern] ',' pattern [',' 'mavenCompatible']] - - property ::= label ':' propertyDefinition (',' propertyDefinition)* nl - propertyDefinition ::= mode '=' (set | prompt) - mode ::= 'quick' | 'new' | 'fill' - set ::= 'set' '(' value ')' - prompt ::= 'prompt' '(' label ')' ('[' default ']')? - - boolean ::= 'true' | 'false' - path, propertyName, label, default ::= text -}}} -The default configuration file for sbt looks like: -{{{ -[scala] - version: read(def.scala.version) -# classifiers: sources - -[app] - org: org.scala-tools.sbt - name: sbt - version: read(sbt.version) - class: sbt.xMain - components: xsbti - cross-versioned: true - -[repositories] - local - maven-local - sbt-db: http://databinder.net/repo/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext] - maven-central - scala-tools-releases - scala-tools-snapshots - -[boot] - directory: project/boot - properties: project/build.properties - 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.organization: new=prompt(Organization) - project.version: quick=set(1.0), new=prompt(Version)[1.0], fill=prompt(Version)[1.0] - def.scala.version: quick=set(2.7.7), new=set(2.7.7), fill=set(2.7.7) - build.scala.versions: quick=set(2.7.7), new=prompt(Scala version)[2.7.7], fill=prompt(Scala version)[2.7.7] - sbt.version: quick=set(0.6.9), new=prompt(sbt version)[0.6.9], fill=prompt(sbt version)[0.6.9] - 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. If specified, the `scala.classifiers`property defines classifiers such as 'sources' of extra Scala artifacts to retrieve. 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 `app.cross-versioned` is true, the resolved module ID is `{app.name+'_'+scala.version}`. The paths given in `app.resources` are added to the application's classpath. If the path is relative, it is resolved against the application's working directory. - -Jars are retrieved to the directory given by `boot.directory`. You can make this an absolute path to be shared by all sbt instances on the machine. You might see messages like: -{{{ - Waiting for lock on to be available... -}}} -if multiple versions access it simultaneously. - -The `boot.properties` property specifies the location of the properties file to use if `app.version` or `scala.version` is specified as `read`. The `prompt-create`, `prompt-fill`, and `quick-option` properties together with the property definitions in `[app.properties]` can be used to initialize the `boot.properties` file. - -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 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 expected 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 properties 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 startup performance and assumes that the jars have already been downloaded. If the directory does not exist, the launcher uses Apache Ivy to resolve and retrieve the jars. A similar process occurs for the application itself. It and its dependencies are retrieved 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' and with the paths specified in `app.resources`. 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. - -== Creating a Launched Application == - -This section shows how to make an application that is launched by this launcher. First, declare a dependency on the launcher-interface. Do not declare a dependency on the launcher itself. The launcher interface consists strictly of Java interfaces in order to avoid binary incompatibility between the version of Scala used to compile the launcher and the version used to compile your application. The launcher interface class will be provided by the launcher, so it is only a compile-time dependency. If you are building with sbt, your dependency definition would be: -{{{ - val launchInterface = "org.scala-tools.sbt" % "launcher-interface" % "0.6.8" % "provided" -}}} -Make the entry point to your class implement 'xsbti.AppMain'. An example that uses some of the information: -{{{ -package xsbt.test -class Main extends xsbti.AppMain -{ - def run(configuration: xsbti.AppConfiguration) = - { - // get the version of Scala used to launch the application - val scalaVersion = configuration.provider.scalaProvider.version - // Print a message and the arguments to the application - println("Hello world! Running Scala " + scalaVersion) - configuration.arguments.foreach(println) - // demonstrate the ability to reboot the application into different versions of Scala - // and how to return the code to exit with - scalaVersion match - { - case "2.7.7" => - new xsbti.Reboot { - def arguments = configuration.arguments - def baseDirectory = configuration.baseDirectory - def scalaVersion = "2.7.4" - def app = configuration.provider.id - } - case "2.7.4" => new Exit(1) - case _ => new Exit(0) - } - } - class Exit(val code) extends xsbti.Exit -} -}}} -Next, define a configuration file for the launcher. For the above class, it might look like: -{{{ -[scala] - version: 2.7.7 -[app] - org: org.scala-tools.sbt - name: xsbt-test - version: 0.6.8 - class: xsbt.test.Main - cross-versioned: true -[repositories] - local - maven-local - maven-central - scala-tools-releases -# scala-tools-snapshots -[boot] - directory: boot -}}} -Then, `publish-local` or `+publish-local` the application to make it available. - -== Running an Application == - -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. - # Replace the /sbt/sbt.boot.properties file in the launcher jar and distribute the modified jar. The user would need a script to run 'java -jar your-launcher.jar arg1 arg2 ...'. - # The user downloads the launcher jar and you provide the configuration file. - # The user needs to run 'java -Dsbt.boot.properties=your.boot.properties -jar launcher.jar'. - # The user already has a script to run the launcher (call it 'launch'). The user needs to run -{{{ - launch @your.boot.properties your-arg-1 your-arg-2 -}}} \ No newline at end of file diff --git a/launch/interface/src/main/java/xsbti/ApplicationID.java b/launch/interface/src/main/java/xsbti/ApplicationID.java index 17331d82af..3f2af6cf7f 100644 --- a/launch/interface/src/main/java/xsbti/ApplicationID.java +++ b/launch/interface/src/main/java/xsbti/ApplicationID.java @@ -11,6 +11,7 @@ public interface ApplicationID public String mainClass(); public String[] mainComponents(); public boolean crossVersioned(); + public CrossValue crossVersionedValue(); /** Files to add to the application classpath. */ public File[] classpathExtra(); diff --git a/launch/interface/src/main/java/xsbti/CrossValue.java b/launch/interface/src/main/java/xsbti/CrossValue.java new file mode 100644 index 0000000000..763cc8be8d --- /dev/null +++ b/launch/interface/src/main/java/xsbti/CrossValue.java @@ -0,0 +1,6 @@ +package xsbti; + +public enum CrossValue +{ + Disabled, Full, Binary; +} diff --git a/launch/src/main/scala/xsbt/boot/ConfigurationParser.scala b/launch/src/main/scala/xsbt/boot/ConfigurationParser.scala index f0122e81d4..4bae3736f6 100644 --- a/launch/src/main/scala/xsbt/boot/ConfigurationParser.scala +++ b/launch/src/main/scala/xsbt/boot/ConfigurationParser.scala @@ -170,12 +170,12 @@ class ConfigurationParser val (rev, m3) = getVersion(m2, name + " version", name + ".version") val (main, m4) = id(m3, "class", "xsbt.Main") val (components, m5) = ids(m4, "components", List("default")) - val (crossVersioned, m6) = id(m5, "cross-versioned", "true") + val (crossVersioned, m6) = id(m5, "cross-versioned", CrossVersionUtil.binaryString) val (resources, m7) = ids(m6, "resources", Nil) val (classifiers, m8) = getClassifiers(m7, "Application classifiers") check(m8, "label") val classpathExtra = toArray(toFiles(resources)) - val app = new Application(org, name, rev, main, components, toBoolean(crossVersioned), classpathExtra) + val app = new Application(org, name, rev, main, components, LaunchCrossVersion(crossVersioned), classpathExtra) (app, classifiers) } def getRepositories(m: LabelMap): List[Repository.Repository] = diff --git a/launch/src/main/scala/xsbt/boot/Launch.scala b/launch/src/main/scala/xsbt/boot/Launch.scala index b345535d96..ec3da2a4aa 100644 --- a/launch/src/main/scala/xsbt/boot/Launch.scala +++ b/launch/src/main/scala/xsbt/boot/Launch.scala @@ -104,7 +104,7 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i def jnaLoader(parent: ClassLoader): ClassLoader = { - val id = AppID("net.java.dev.jna", "jna", "3.2.3", "", toArray(Nil), false, array()) + val id = AppID("net.java.dev.jna", "jna", "3.2.3", "", toArray(Nil), xsbti.CrossValue.Disabled, array()) val configuration = makeConfiguration(ScalaOrg, None) val jnaHome = appDirectory(new File(bootDirectory, baseDirectoryName(ScalaOrg, None)), id) val module = appModule(id, None, false, "jna") diff --git a/launch/src/main/scala/xsbt/boot/LaunchConfiguration.scala b/launch/src/main/scala/xsbt/boot/LaunchConfiguration.scala index 7fa9b1ef5b..637f4a8e6e 100644 --- a/launch/src/main/scala/xsbt/boot/LaunchConfiguration.scala +++ b/launch/src/main/scala/xsbt/boot/LaunchConfiguration.scala @@ -50,21 +50,35 @@ object Classifiers { def apply(forScala: List[String], app: List[String]):Classifiers = Classifiers(new Explicit(forScala), new Explicit(app)) } -final case class Application(groupID: String, name: String, version: Value[String], main: String, components: List[String], crossVersioned: Boolean, classpathExtra: Array[File]) +object LaunchCrossVersion +{ + def apply(s: String): xsbti.CrossValue = + s match { + case x if CrossVersionUtil.isFull(s) => xsbti.CrossValue.Full + case x if CrossVersionUtil.isBinary(s) => xsbti.CrossValue.Binary + case x if CrossVersionUtil.isDisabled(s) => xsbti.CrossValue.Disabled + case x => error("Unknown value '" + x + "' for property 'cross-versioned'") + } +} + +final case class Application(groupID: String, name: String, version: Value[String], main: String, components: List[String], crossVersioned: xsbti.CrossValue, classpathExtra: Array[File]) { def getVersion = Value.get(version) def withVersion(newVersion: Value[String]) = Application(groupID, name, newVersion, main, components, crossVersioned, classpathExtra) def toID = AppID(groupID, name, getVersion, main, toArray(components), crossVersioned, classpathExtra) def map(f: File => File) = Application(groupID, name, version, main, components, crossVersioned, classpathExtra.map(f)) } -final case class AppID(groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersioned: Boolean, classpathExtra: Array[File]) extends xsbti.ApplicationID +final case class AppID(groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersionedValue: xsbti.CrossValue, classpathExtra: Array[File]) extends xsbti.ApplicationID +{ + def crossVersioned: Boolean = crossVersionedValue != xsbti.CrossValue.Disabled +} object Application { def apply(id: xsbti.ApplicationID): Application = { import id._ - Application(groupID, name, new Explicit(version), mainClass, mainComponents.toList, crossVersioned, classpathExtra) + Application(groupID, name, new Explicit(version), mainClass, mainComponents.toList, crossVersionedValue, classpathExtra) } } diff --git a/launch/src/main/scala/xsbt/boot/Update.scala b/launch/src/main/scala/xsbt/boot/Update.scala index dddb310140..063e9d9083 100644 --- a/launch/src/main/scala/xsbt/boot/Update.scala +++ b/launch/src/main/scala/xsbt/boot/Update.scala @@ -138,7 +138,8 @@ final class Update(config: UpdateConfiguration) case u: UpdateApp => val app = u.id val resolvedName = (app.crossVersioned, scalaVersion) match { - case (true, Some(sv)) => app.name + "_" + sv + case (xsbti.CrossValue.Full, Some(sv)) => app.name + "_" + sv + case (xsbti.CrossValue.Binary, Some(sv)) => app.name + "_" + CrossVersionUtil.binaryScalaVersion(sv) case _ => app.name } addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", u.classifiers) diff --git a/main/command/src/main/scala/sbt/MainControl.scala b/main/command/src/main/scala/sbt/MainControl.scala index edc60bd904..87612ef733 100644 --- a/main/command/src/main/scala/sbt/MainControl.scala +++ b/main/command/src/main/scala/sbt/MainControl.scala @@ -13,15 +13,16 @@ final case class Reboot(scalaVersion: String, argsList: Seq[String], app: xsbti. { def arguments = argsList.toArray } -final case class ApplicationID(groupID: String, name: String, version: String, mainClass: String, components: Seq[String], crossVersioned: Boolean, extra: Seq[File]) extends xsbti.ApplicationID +final case class ApplicationID(groupID: String, name: String, version: String, mainClass: String, components: Seq[String], crossVersionedValue: xsbti.CrossValue, extra: Seq[File]) extends xsbti.ApplicationID { def mainComponents = components.toArray def classpathExtra = extra.toArray + def crossVersioned = crossVersionedValue != xsbti.CrossValue.Disabled } object ApplicationID { def apply(delegate: xsbti.ApplicationID, newVersion: String): ApplicationID = apply(delegate).copy(version = newVersion) def apply(delegate: xsbti.ApplicationID): ApplicationID = - ApplicationID(delegate.groupID, delegate.name, delegate.version, delegate.mainClass, delegate.mainComponents, delegate.crossVersioned, delegate.classpathExtra) + ApplicationID(delegate.groupID, delegate.name, delegate.version, delegate.mainClass, delegate.mainComponents, delegate.crossVersionedValue, delegate.classpathExtra) } \ No newline at end of file diff --git a/project/Sbt.scala b/project/Sbt.scala index d4fb82028a..4aaec1a004 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -71,11 +71,13 @@ object Sbt extends Build lazy val classfileSub = testedBaseProject(utilPath / "classfile", "Classfile") dependsOn(ioSub, interfaceSub, logSub) // generates immutable or mutable Java data types according to a simple input format lazy val datatypeSub = baseProject(utilPath /"datatype", "Datatype Generator") dependsOn(ioSub) + // cross versioning + lazy val crossSub = baseProject(utilPath / "cross", "Cross") settings(inConfig(Compile)(Transform.crossGenSettings): _*) /* **** Intermediate-level Modules **** */ // Apache Ivy integration - lazy val ivySub = baseProject(file("ivy"), "Ivy") dependsOn(interfaceSub, launchInterfaceSub, logSub % "compile;test->test", ioSub % "compile;test->test", launchSub % "test->test") settings(ivy, jsch, httpclient, testExclusive) + lazy val ivySub = baseProject(file("ivy"), "Ivy") dependsOn(interfaceSub, launchInterfaceSub, crossSub, logSub % "compile;test->test", ioSub % "compile;test->test", launchSub % "test->test") settings(ivy, jsch, httpclient, testExclusive) // Runner for uniform test interface lazy val testingSub = baseProject(file("testing"), "Testing") dependsOn(ioSub, classpathSub, logSub, launchInterfaceSub, testAgentSub) settings(libraryDependencies += "org.scala-tools.testing" % "test-interface" % "0.5") // Testing agent for running tests in a separate process. @@ -123,7 +125,7 @@ object Sbt extends Build interfaceSub, ioSub, ivySub, logSub, processSub, runSub, relationSub, stdTaskSub, taskSub, trackingSub, testingSub) // General command support and core commands not specific to a build system - lazy val commandSub = testedBaseProject(mainPath / "command", "Command") dependsOn(interfaceSub, ioSub, launchInterfaceSub, logSub, completeSub, classpathSub) + lazy val commandSub = testedBaseProject(mainPath / "command", "Command") dependsOn(interfaceSub, ioSub, launchInterfaceSub, logSub, completeSub, classpathSub, crossSub) // Fixes scope=Scope for Setting (core defined in collectionSub) to define the settings system used in build definitions lazy val mainSettingsSub = testedBaseProject(mainPath / "settings", "Main Settings") dependsOn(applyMacroSub, interfaceSub, ivySub, relationSub, logSub, ioSub, commandSub, completeSub, classpathSub, stdTaskSub, processSub) settings( sbinary ) @@ -187,10 +189,16 @@ object Sbt extends Build def deep[T](scoped: ScopedSetting[T]): Initialize[Seq[T]] = Util.inAllProjects(projects filterNot Set(root, sbtSub, scriptedBaseSub, scriptedSbtSub, scriptedPluginSub) map { p => LocalProject(p.id) }, scoped) - def launchSettings = inConfig(Compile)(Transform.configSettings) ++ Seq(jline, ivy, crossPaths := false, - compile in Test <<= compile in Test dependsOn(publishLocal in interfaceSub, publishLocal in testSamples, publishLocal in launchInterfaceSub) -// mappings in (Compile, packageBin) <++= (mappings in (launchInterfaceSub, Compile, packageBin) ).identity - ) + def launchSettings = + Seq(jline, ivy, crossPaths := false, + compile in Test <<= compile in Test dependsOn(publishLocal in interfaceSub, publishLocal in testSamples, publishLocal in launchInterfaceSub) + // mappings in (Compile, packageBin) <++= (mappings in (launchInterfaceSub, Compile, packageBin) ).identity + ) ++ + inConfig(Compile)(Transform.configSettings) ++ + inConfig(Compile)(Transform.transSourceSettings ++ Seq( + Transform.inputSourceDirectory <<= (sourceDirectory in crossSub) / "input_sources", + Transform.sourceProperties := Map("cross.package0" -> "xsbt", "cross.package1" -> "boot") + )) import Sxr.sxr def releaseSettings = Release.settings(nonRoots, proguard in Proguard) diff --git a/project/Transform.scala b/project/Transform.scala index 9d01a18665..496291351d 100644 --- a/project/Transform.scala +++ b/project/Transform.scala @@ -3,6 +3,12 @@ object Transform { + lazy val transformSources = TaskKey[Seq[File]]("transform-sources") + lazy val inputSourceDirectories = SettingKey[Seq[File]]("input-source-directories") + lazy val inputSourceDirectory = SettingKey[File]("input-source-directory") + lazy val inputSources = TaskKey[Seq[File]]("input-sources") + lazy val sourceProperties = TaskKey[Map[String,String]]("source-properties") + lazy val transformResources = TaskKey[Seq[File]]("transform-resources") lazy val inputResourceDirectories = SettingKey[Seq[File]]("input-resource-directories") lazy val inputResourceDirectory = SettingKey[File]("input-resource-directory") @@ -11,24 +17,40 @@ object Transform // to be replace by 0.10.1's fileMappings lazy val fileMappings = TaskKey[Seq[(File,File)]]("file-mappings") - def configSettings = Seq( + def crossGenSettings = transSourceSettings ++ seq( + sourceProperties := Map("cross.package0" -> "sbt", "cross.package1" -> "cross") + ) + def transSourceSettings = seq( + inputSourceDirectory <<= sourceDirectory / "input_sources", + inputSourceDirectories <<= Seq(inputSourceDirectory).join, + inputSources <<= inputSourceDirectories.map(dirs => (dirs ** (-DirectoryFilter)).get ), + fileMappings in transformSources <<= transformSourceMappings, + transformSources <<= (fileMappings in transformSources, sourceProperties) map { (rs, props) => + rs map { case (in, out) => transform(in, out, props) } + }, + sourceGenerators <+= transformSources + ) + def transformSourceMappings = (inputSources, inputSourceDirectories, sourceManaged) map { (ss, sdirs, sm) => + (ss --- sdirs) x (rebase(sdirs, sm)|flat(sm)) toSeq + } + def configSettings = transResourceSettings ++ seq( + resourceProperties <<= (organization, version, scalaVersion, Status.isSnapshot) map { (org, v, sv, isSnapshot) => + Map("org" -> org, "sbt.version" -> v, "scala.version" -> sv, "repositories" -> repositories(isSnapshot).mkString(IO.Newline)) + } + ) + def transResourceSettings = seq( inputResourceDirectory <<= sourceDirectory / "input_resources", inputResourceDirectories <<= Seq(inputResourceDirectory).join, inputResources <<= inputResourceDirectories.map(dirs => (dirs ** (-DirectoryFilter)).get ), - resourceProperties <<= defineProperties, - fileMappings in transformResources <<= transformMappings, + fileMappings in transformResources <<= transformResourceMappings, transformResources <<= (fileMappings in transformResources, resourceProperties) map { (rs, props) => rs map { case (in, out) => transform(in, out, props) } }, resourceGenerators <+= transformResources ) - def transformMappings = (inputResources, inputResourceDirectories, resourceManaged) map { (rs, rdirs, rm) => + def transformResourceMappings = (inputResources, inputResourceDirectories, resourceManaged) map { (rs, rdirs, rm) => (rs --- rdirs) x (rebase(rdirs, rm)|flat(rm)) toSeq } - def defineProperties = - (organization, version, scalaVersion, Status.isSnapshot) map { (org, v, sv, isSnapshot) => - Map("org" -> org, "sbt.version" -> v, "scala.version" -> sv, "repositories" -> repositories(isSnapshot).mkString(IO.Newline)) - } def transform(in: File, out: File, map: Map[String, String]): File = { diff --git a/src/sphinx/Detailed-Topics/Launcher.rst b/src/sphinx/Detailed-Topics/Launcher.rst index b29f2f512d..285247f2ed 100644 --- a/src/sphinx/Detailed-Topics/Launcher.rst +++ b/src/sphinx/Detailed-Topics/Launcher.rst @@ -102,7 +102,7 @@ by the following grammar. ``'nl'`` is a newline or end of file and name: "name" ":" text class: "class" ":" text components: "components" ":" `component` ("," `component`)* - crossVersioned: "cross-versioned" ":" `boolean` + crossVersioned: "cross-versioned" ":" ("true" | "false" | "none" | "binary" | "full") resources: "resources" ":" `path` ("," `path`)* repository: ( `predefinedRepository` | `customRepository` ) `nl` predefinedRepository: "local" | "maven-local" | "maven-central" @@ -182,8 +182,10 @@ classifiers, such as 'sources', of extra Scala artifacts to retrieve. 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 -``app.cross-versioned`` is true, the resolved module ID is +from the repositories listed in ``[repositories]``. If +``app.cross-versioned`` is ``binary``, the resolved module ID is +``{app.name+'_'+CrossVersion.binaryScalaVersion(scala.version)}``. +If ``app.cross-versioned`` is ``true`` or ``full``, the resolved module ID is ``{app.name+'_'+scala.version}``. The ``scala.version`` property must be specified and cannot be ``auto`` when cross-versioned. The paths given in ``app.resources`` are added to the application's classpath. If the @@ -356,7 +358,7 @@ it might look like: name: xsbt-test version: 0.12.0 class: xsbt.test.Main - cross-versioned: true + cross-versioned: binary [repositories] local maven-central diff --git a/util/cross/src/main/input_sources/CrossVersionUtil.scala b/util/cross/src/main/input_sources/CrossVersionUtil.scala new file mode 100644 index 0000000000..c22106b2ef --- /dev/null +++ b/util/cross/src/main/input_sources/CrossVersionUtil.scala @@ -0,0 +1,69 @@ +package ${{cross.package0}}.${{cross.package1}} + +object CrossVersionUtil +{ + val trueString = "true" + val falseString = "false" + val fullString = "full" + val noneString = "none" + val disabledString = "disabled" + val binaryString = "binary" + val TransitionScalaVersion = "2.10" + val TransitionSbtVersion = "0.12" + + def isFull(s: String): Boolean = (s == trueString) || (s == fullString) + def isDisabled(s: String): Boolean = (s == falseString) || (s == noneString) || (s == disabledString) + def isBinary(s: String): Boolean = (s == binaryString) + + private[${{cross.package0}}] def isSbtApiCompatible(v: String): Boolean = sbtApiVersion(v).isDefined + /** Returns sbt binary interface x.y API compatible with the given version string v. + * RCs for x.y.0 are considered API compatible. + * Compatibile versions include 0.12.0-1 and 0.12.0-RC1 for Some(0, 12). + */ + private[${{cross.package0}}] def sbtApiVersion(v: String): Option[(Int, Int)] = + { + val ReleaseV = """(\d+)\.(\d+)\.(\d+)(-\d+)?""".r + val CandidateV = """(\d+)\.(\d+)\.(\d+)(-RC\d+)""".r + val NonReleaseV = """(\d+)\.(\d+)\.(\d+)(-\w+)""".r + v match { + case ReleaseV(x, y, z, ht) => Some((x.toInt, y.toInt)) + case CandidateV(x, y, z, ht) => Some((x.toInt, y.toInt)) + case NonReleaseV(x, y, z, ht) if z.toInt > 0 => Some((x.toInt, y.toInt)) + case _ => None + } + } + private[${{cross.package0}}] def isScalaApiCompatible(v: String): Boolean = scalaApiVersion(v).isDefined + /** Returns Scala binary interface x.y API compatible with the given version string v. + * Compatibile versions include 2.10.0-1 and 2.10.1-M1 for Some(2, 10), but not 2.10.0-RC1. + */ + private[${{cross.package0}}] def scalaApiVersion(v: String): Option[(Int, Int)] = + { + val ReleaseV = """(\d+)\.(\d+)\.(\d+)(-\d+)?""".r + val NonReleaseV = """(\d+)\.(\d+)\.(\d+)(-\w+)""".r + v match { + case ReleaseV(x, y, z, ht) => Some((x.toInt, y.toInt)) + case NonReleaseV(x, y, z, ht) if z.toInt > 0 => Some((x.toInt, y.toInt)) + case _ => None + } + } + private[${{cross.package0}}] val PartialVersion = """(\d+)\.(\d+)(?:\..+)?""".r + private[${{cross.package0}}] def partialVersion(s: String): Option[(Int,Int)] = + s match { + case PartialVersion(major, minor) => Some(major.toInt, minor.toInt) + case _ => None + } + def binaryScalaVersion(full: String): String = binaryVersionWithApi(full, TransitionScalaVersion)(scalaApiVersion) + def binarySbtVersion(full: String): String = binaryVersionWithApi(full, TransitionSbtVersion)(sbtApiVersion) + private[${{cross.package0}}] def binaryVersion(full: String, cutoff: String): String = binaryVersionWithApi(full, cutoff)(scalaApiVersion) + private[this] def isNewer(major: Int, minor: Int, minMajor: Int, minMinor: Int): Boolean = + major > minMajor || (major == minMajor && minor >= minMinor) + private[this] def binaryVersionWithApi(full: String, cutoff: String)(apiVersion: String => Option[(Int,Int)]): String = + { + def sub(major: Int, minor: Int) = major + "." + minor + (apiVersion(full), partialVersion(cutoff)) match { + case (Some((major, minor)), None) => sub(major, minor) + case (Some((major, minor)), Some((minMajor, minMinor))) if isNewer(major, minor, minMajor, minMinor) => sub(major, minor) + case _ => full + } + } +}