mirror of https://github.com/sbt/sbt.git
sbt and Scala classifiers can be configured in build.properties
The sbt.classifiers property sets the classifiers for sbt and scala.classifiers sets it for Scala. Note that when creating a project, Scala and sbt are retrieved right after project setup. Changes to *.classifiers will only apply if project/boot is removed or a new version of Scala is retrieved.
This commit is contained in:
parent
e750de2c3b
commit
dff30c036e
|
|
@ -5,12 +5,20 @@ package xsbt.boot
|
|||
|
||||
|
||||
import Pre._
|
||||
import ConfigurationParser._
|
||||
import java.lang.Character.isWhitespace
|
||||
import java.io.{BufferedReader, File, FileInputStream, InputStreamReader, Reader, StringReader}
|
||||
import java.net.{MalformedURLException, URL}
|
||||
import java.util.regex.Pattern
|
||||
import scala.collection.immutable.List
|
||||
|
||||
object ConfigurationParser
|
||||
{
|
||||
def trim(s: Array[String]) = s.map(_.trim).toList
|
||||
def ids(value: String) = trim(value.split(",")).filter(isNonEmpty)
|
||||
|
||||
implicit val readIDs = ids _
|
||||
}
|
||||
class ConfigurationParser extends NotNull
|
||||
{
|
||||
def apply(file: File): LaunchConfiguration = Using(new InputStreamReader(new FileInputStream(file), "UTF-8"))(apply)
|
||||
|
|
@ -36,24 +44,30 @@ class ConfigurationParser extends NotNull
|
|||
val (properties, m6) = processSection(m5, "app-properties", getAppProperties)
|
||||
val (cacheDir, m7) = processSection(m6, "ivy", getIvy)
|
||||
check(m7, "section")
|
||||
val classifiers = Classifiers("" :: scalaClassifiers, "" :: appClassifiers) // the added "" ensures that the main jars are retrieved
|
||||
val classifiers = Classifiers(scalaClassifiers, appClassifiers)
|
||||
new LaunchConfiguration(scalaVersion, IvyOptions(cacheDir, classifiers, repositories), app, boot, logging, properties)
|
||||
}
|
||||
def getScala(m: LabelMap) =
|
||||
{
|
||||
val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version")
|
||||
val (scalaClassifiers, m2) = ids(m1, "classifiers", Nil)
|
||||
val (scalaClassifiers, m2) = getClassifiers(m1, "Scala classifiers")
|
||||
check(m2, "label")
|
||||
(scalaVersion, scalaClassifiers)
|
||||
}
|
||||
def getVersion(m: LabelMap, label: String, defaultName: String): (Version, LabelMap) = process(m, "version", processVersion(label, defaultName))
|
||||
def processVersion(label: String, defaultName: String)(value: Option[String]): Version =
|
||||
value.map(version(label)).getOrElse(new Version.Implicit(defaultName, None))
|
||||
def version(label: String)(value: String): Version =
|
||||
def getClassifiers(m: LabelMap, label: String): (Value[List[String]], LabelMap) =
|
||||
process(m, "classifiers", processClassifiers(label))
|
||||
def processClassifiers(label: String)(value: Option[String]): Value[List[String]] =
|
||||
value.map(readValue[List[String]](label)) getOrElse new Explicit(Nil)
|
||||
|
||||
def getVersion(m: LabelMap, label: String, defaultName: String): (Value[String], LabelMap) = process(m, "version", processVersion(label, defaultName))
|
||||
def processVersion(label: String, defaultName: String)(value: Option[String]): Value[String] =
|
||||
value.map(readValue[String](label)).getOrElse(new Implicit(defaultName, None))
|
||||
|
||||
def readValue[T](label: String)(implicit read: String => T): String => Value[T] = value =>
|
||||
{
|
||||
if(isEmpty(value)) error(label + " cannot be empty (omit version declaration to use the default version)")
|
||||
try { parsePropertyValue(label, value)(Version.Implicit.apply) }
|
||||
catch { case e: BootException => new Version.Explicit(value) }
|
||||
if(isEmpty(value)) error(label + " cannot be empty (omit declaration to use the default)")
|
||||
try { parsePropertyValue(label, value)(Value.readImplied[T]) }
|
||||
catch { case e: BootException => new Explicit(read(value)) }
|
||||
}
|
||||
def processSection[T](sections: SectionMap, name: String, f: LabelMap => T) =
|
||||
process[String,LabelMap,T](sections, name, m => f(m default(x => None)))
|
||||
|
|
@ -65,7 +79,7 @@ class ConfigurationParser extends NotNull
|
|||
def getOrNone[K,V](map: ListMap[K,Option[V]], k: K) = orElse(map.get(k), None)
|
||||
def ids(map: LabelMap, name: String, default: List[String]) =
|
||||
{
|
||||
val result = map(name).map(value => trim(value.split(",")).filter(isNonEmpty))
|
||||
val result = map(name) map ConfigurationParser.ids
|
||||
(orElse(result, default), map - name)
|
||||
}
|
||||
def bool(map: LabelMap, name: String, default: Boolean): (Boolean, LabelMap) =
|
||||
|
|
@ -105,7 +119,7 @@ class ConfigurationParser extends NotNull
|
|||
case (tpe :: paths, newM) => (Search(tpe, toFiles(paths)), newM)
|
||||
}
|
||||
|
||||
def getApplication(m: LabelMap): (Application, List[String]) =
|
||||
def getApplication(m: LabelMap): (Application, Value[List[String]]) =
|
||||
{
|
||||
val (org, m1) = id(m, "org", "org.scala-tools.sbt")
|
||||
val (name, m2) = id(m1, "name", "sbt")
|
||||
|
|
@ -114,7 +128,7 @@ class ConfigurationParser extends NotNull
|
|||
val (components, m5) = ids(m4, "components", List("default"))
|
||||
val (crossVersioned, m6) = id(m5, "cross-versioned", "true")
|
||||
val (resources, m7) = ids(m6, "resources", Nil)
|
||||
val (classifiers, m8) = ids(m7, "classifiers", 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)
|
||||
|
|
@ -156,7 +170,6 @@ class ConfigurationParser extends NotNull
|
|||
val optionalArg = m.group(3)
|
||||
f(m.group(1), m.group(2), if(optionalArg eq null) None else Some(optionalArg))
|
||||
}
|
||||
def trim(s: Array[String]) = s.map(_.trim).toList
|
||||
|
||||
type LabelMap = ListMap[String, Option[String]]
|
||||
// section-name -> label -> value
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ object Launch
|
|||
def initialized(currentDirectory: File, parsed: LaunchConfiguration, arguments: List[String]): Unit =
|
||||
{
|
||||
parsed.logging.debug("Parsed configuration: " + parsed)
|
||||
val resolved = ResolveVersions(parsed)
|
||||
val resolved = ResolveValues(parsed)
|
||||
resolved.logging.debug("Resolved configuration: " + resolved)
|
||||
explicit(currentDirectory, resolved, arguments)
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ class Launch private[xsbt](val bootDirectory: File, val ivyOptions: IvyOptions)
|
|||
override def classpath = array(compilerJar, libraryJar)
|
||||
def baseDirectories = List(scalaHome)
|
||||
def testLoadClasses = TestLoadScalaClasses
|
||||
def target = new UpdateScala(classifiers.forScala)
|
||||
def target = new UpdateScala(Value.get(classifiers.forScala))
|
||||
def failLabel = "Scala " + version
|
||||
def lockFile = updateLockFile
|
||||
def extraClasspath = array()
|
||||
|
|
@ -106,7 +106,7 @@ class Launch private[xsbt](val bootDirectory: File, val ivyOptions: IvyOptions)
|
|||
def parentLoader = ScalaProvider.this.loader
|
||||
def baseDirectories = appHome :: id.mainComponents.map(components.componentLocation).toList
|
||||
def testLoadClasses = List(id.mainClass)
|
||||
def target = new UpdateApp(Application(id), classifiers.app)
|
||||
def target = new UpdateApp(Application(id), Value.get(classifiers.app))
|
||||
def failLabel = id.name + " " + id.version
|
||||
def lockFile = updateLockFile
|
||||
def mainClasspath = fullClasspath
|
||||
|
|
@ -140,7 +140,7 @@ object Launcher
|
|||
{
|
||||
val parsed = ResolvePaths(Configuration.parse(configLocation, baseDirectory), baseDirectory)
|
||||
Initialize.process(parsed.boot.properties, parsed.appProperties, Initialize.selectQuick)
|
||||
val config = ResolveVersions(parsed)
|
||||
val config = ResolveValues(parsed)
|
||||
val launcher = apply(config)
|
||||
val scalaProvider = launcher.getScala(config.getScalaVersion)
|
||||
scalaProvider.app(config.app.toID)
|
||||
|
|
|
|||
|
|
@ -8,40 +8,45 @@ import java.io.File
|
|||
import java.net.URL
|
||||
import scala.collection.immutable.List
|
||||
|
||||
final case class LaunchConfiguration(scalaVersion: Version, ivyConfiguration: IvyOptions, app: Application, boot: BootSetup, logging: Logging, appProperties: List[AppProperty]) extends NotNull
|
||||
//TODO: use copy constructor, check size change
|
||||
final case class LaunchConfiguration(scalaVersion: Value[String], ivyConfiguration: IvyOptions, app: Application, boot: BootSetup, logging: Logging, appProperties: List[AppProperty]) extends NotNull
|
||||
{
|
||||
def getScalaVersion = Version.get(scalaVersion)
|
||||
def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), ivyConfiguration, app, boot, logging, appProperties)
|
||||
def getScalaVersion = Value.get(scalaVersion)
|
||||
def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(new Explicit(newScalaVersion), ivyConfiguration, app, boot, logging, appProperties)
|
||||
def withApp(app: Application) = LaunchConfiguration(scalaVersion, ivyConfiguration, app, boot, logging, appProperties)
|
||||
def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, ivyConfiguration, app.withVersion(new Version.Explicit(newAppVersion)), boot, logging, appProperties)
|
||||
def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), ivyConfiguration, app.withVersion(new Version.Explicit(newAppVersion)), boot, logging, appProperties)
|
||||
def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, ivyConfiguration, app.withVersion(new Explicit(newAppVersion)), boot, logging, appProperties)
|
||||
// TODO: withExplicit
|
||||
def withVersions(newScalaVersion: String, newAppVersion: String, classifiers0: Classifiers) =
|
||||
LaunchConfiguration(new Explicit(newScalaVersion), ivyConfiguration.copy(classifiers = classifiers0), app.withVersion(new Explicit(newAppVersion)), boot, logging, appProperties)
|
||||
def map(f: File => File) = LaunchConfiguration(scalaVersion, ivyConfiguration, app.map(f), boot.map(f), logging, appProperties)
|
||||
}
|
||||
final case class IvyOptions(cacheDirectory: Option[File], classifiers: Classifiers, repositories: List[Repository]) extends NotNull
|
||||
final case class Classifiers(forScala: List[String], app: List[String]) extends NotNull
|
||||
sealed trait Version extends NotNull
|
||||
object Version
|
||||
{
|
||||
final class Explicit(val value: String) extends Version { override def toString = value }
|
||||
final class Implicit(val name: String, val default: Option[String]) extends Version
|
||||
{
|
||||
require(isNonEmpty(name), "Name cannot be empty")
|
||||
require(default.isEmpty || isNonEmpty(default.get), "Default cannot be the empty string")
|
||||
override def toString = name + (default match { case Some(d) => "[" + d + "]"; case None => "" })
|
||||
}
|
||||
|
||||
object Implicit
|
||||
{
|
||||
def apply(s: String, name: String, default: Option[String]): Version =
|
||||
if(s == "read") new Implicit(name, default) else error("Expected 'read', got '" + s +"'")
|
||||
}
|
||||
def get(v: Version) = v match { case e: Version.Explicit => e.value; case _ => throw new BootException("Unresolved version: " + v) }
|
||||
sealed trait Value[T]
|
||||
final class Explicit[T](val value: T) extends Value[T] {
|
||||
override def toString = value.toString
|
||||
}
|
||||
final class Implicit[T](val name: String, val default: Option[T]) extends Value[T]
|
||||
{
|
||||
require(isNonEmpty(name), "Name cannot be empty")
|
||||
override def toString = name + (default match { case Some(d) => "[" + d + "]"; case None => "" })
|
||||
}
|
||||
object Value
|
||||
{
|
||||
def get[T](v: Value[T]): T = v match { case e: Explicit[T] => e.value; case _ => throw new BootException("Unresolved version: " + v) }
|
||||
def readImplied[T](s: String, name: String, default: Option[String])(implicit read: String => T): Value[T] =
|
||||
if(s == "read") new Implicit(name, default map read) else error("Expected 'read', got '" + s +"'")
|
||||
}
|
||||
|
||||
final case class Application(groupID: String, name: String, version: Version, main: String, components: List[String], crossVersioned: Boolean, classpathExtra: Array[File]) extends NotNull
|
||||
final case class Classifiers(forScala: Value[List[String]], app: Value[List[String]])
|
||||
object Classifiers {
|
||||
def apply(forScala: List[String], app: List[String]):Classifiers = Classifiers(new Explicit(forScala), new Explicit(app))
|
||||
}
|
||||
|
||||
final case class Application(groupID: String, name: String, version: Value[String], main: String, components: List[String], crossVersioned: Boolean, classpathExtra: Array[File]) extends NotNull
|
||||
{
|
||||
def getVersion = Version.get(version)
|
||||
def withVersion(newVersion: Version) = Application(groupID, name, newVersion, main, components, crossVersioned, classpathExtra)
|
||||
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))
|
||||
}
|
||||
|
|
@ -52,7 +57,7 @@ object Application
|
|||
def apply(id: xsbti.ApplicationID): Application =
|
||||
{
|
||||
import id._
|
||||
Application(groupID, name, new Version.Explicit(version), mainClass, mainComponents.toList, crossVersioned, classpathExtra)
|
||||
Application(groupID, name, new Explicit(version), mainClass, mainComponents.toList, crossVersioned, classpathExtra)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,10 +105,9 @@ final class PromptProperty(val label: String, val default: Option[String]) exten
|
|||
|
||||
final class Logging(level: LogLevel.Value) extends NotNull
|
||||
{
|
||||
import LogLevel._
|
||||
def log(s: => String, at: Value) = if(level.id <= at.id) stream(at).println("[" + at + "] " + s)
|
||||
def debug(s: => String) = log(s, Debug)
|
||||
private def stream(at: Value) = if(at == Error) System.err else System.out
|
||||
def log(s: => String, at: LogLevel.Value) = if(level.id <= at.id) stream(at).println("[" + at + "] " + s)
|
||||
def debug(s: => String) = log(s, LogLevel.Debug)
|
||||
private def stream(at: LogLevel.Value) = if(at == LogLevel.Error) System.err else System.out
|
||||
}
|
||||
object LogLevel extends Enumeration
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ trait Provider extends NotNull
|
|||
def retrieveCorrupt(missing: Iterable[String]): Nothing = fail(": missing " + missing.mkString(", "))
|
||||
private def fail(extra: String) =
|
||||
throw new xsbti.RetrieveException(versionString, "Could not retrieve " + failLabel + extra)
|
||||
private def versionString: String = target match { case _: UpdateScala => configuration.scalaVersion; case a: UpdateApp => Version.get(a.id.version) }
|
||||
private def versionString: String = target match { case _: UpdateScala => configuration.scalaVersion; case a: UpdateApp => Value.get(a.id.version) }
|
||||
|
||||
val (jars, loader) = Locks(lockFile, new initialize)
|
||||
private final class initialize extends Callable[(Array[File], ClassLoader)]
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import Pre._
|
|||
import java.io.{File, FileInputStream}
|
||||
import java.util.Properties
|
||||
|
||||
object ResolveVersions
|
||||
object ResolveValues
|
||||
{
|
||||
def apply(conf: LaunchConfiguration): LaunchConfiguration = (new ResolveVersions(conf))()
|
||||
def apply(conf: LaunchConfiguration): LaunchConfiguration = (new ResolveValues(conf))()
|
||||
private def trim(s: String) = if(s eq null) None else notEmpty(s.trim)
|
||||
private def notEmpty(s: String) = if(isEmpty(s)) None else Some(s)
|
||||
private def readProperties(propertiesFile: File) =
|
||||
|
|
@ -21,8 +21,8 @@ object ResolveVersions
|
|||
}
|
||||
}
|
||||
|
||||
import ResolveVersions.{readProperties, trim}
|
||||
final class ResolveVersions(conf: LaunchConfiguration) extends NotNull
|
||||
import ResolveValues.{readProperties, trim}
|
||||
final class ResolveValues(conf: LaunchConfiguration) extends NotNull
|
||||
{
|
||||
private def propertiesFile = conf.boot.properties
|
||||
private lazy val properties = readProperties(propertiesFile)
|
||||
|
|
@ -31,17 +31,24 @@ final class ResolveVersions(conf: LaunchConfiguration) extends NotNull
|
|||
import conf._
|
||||
val scalaVersion = resolve(conf.scalaVersion)
|
||||
val appVersion = resolve(app.version)
|
||||
withVersions(scalaVersion, appVersion)
|
||||
val classifiers = resolveClassifiers(ivyConfiguration.classifiers)
|
||||
withVersions(scalaVersion, appVersion, classifiers)
|
||||
}
|
||||
def resolve(v: Version): String =
|
||||
def resolveClassifiers(classifiers: Classifiers): Classifiers =
|
||||
{
|
||||
import ConfigurationParser.readIDs
|
||||
// the added "" ensures that the main jars are retrieved
|
||||
val scalaClassifiers = "" :: resolve(classifiers.forScala)
|
||||
val appClassifiers = "" :: resolve(classifiers.app)
|
||||
Classifiers(new Explicit(scalaClassifiers), new Explicit(appClassifiers))
|
||||
}
|
||||
def resolve[T](v: Value[T])(implicit read: String => T): T =
|
||||
v match
|
||||
{
|
||||
case e: Version.Explicit => e.value
|
||||
case i: Version.Implicit =>
|
||||
trim(properties.getProperty(i.name)) orElse
|
||||
case e: Explicit[t] => e.value
|
||||
case i: Implicit[t] =>
|
||||
trim(properties.getProperty(i.name)) map read orElse
|
||||
i.default getOrElse
|
||||
error("No " + i.name + " specified in " + propertiesFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[scala]
|
||||
version: 2.8.0
|
||||
#classifiers: sources, javadoc
|
||||
classifiers: read(scala.classifiers)[]
|
||||
|
||||
[app]
|
||||
org: org.scala-tools.sbt
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
class: sbt.xMain
|
||||
components: xsbti
|
||||
cross-versioned: true
|
||||
classifiers: read(sbt.classifiers)[]
|
||||
#classifiers: sources, javadoc
|
||||
|
||||
[repositories]
|
||||
|
|
|
|||
Loading…
Reference in New Issue