From 33434bc82b4b0459dfc082bc835f4f53ab6faee2 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 17 Oct 2009 22:40:02 -0400 Subject: [PATCH] Cut size of final jar by 300k. --- launch/Boot.scala | 2 +- launch/Cache.scala | 19 ++ launch/CheckProxy.scala | 3 +- launch/Configuration.scala | 19 +- launch/ConfigurationParser.scala | 210 +++++++++--------- launch/Create.scala | 21 +- launch/Enumeration.scala | 29 +++ launch/Find.scala | 1 + launch/Launch.scala | 57 +++-- launch/LaunchConfiguration.scala | 83 ++++--- launch/ListMap.scala | 32 +++ launch/Locks.scala | 10 +- launch/Pre.scala | 29 +++ launch/Provider.scala | 13 +- launch/ResolveVersions.scala | 7 +- launch/Update.scala | 27 ++- launch/Using.scala | 2 +- launch/src/test/scala/ScalaProviderTest.scala | 16 +- main/Main.scala | 12 - project/build.properties | 2 +- project/build/LauncherProguard.scala | 17 +- project/build/ProguardProject.scala | 3 +- project/build/XSbt.scala | 1 - 23 files changed, 367 insertions(+), 248 deletions(-) create mode 100644 launch/Cache.scala create mode 100644 launch/Enumeration.scala create mode 100644 launch/ListMap.scala create mode 100644 launch/Pre.scala delete mode 100644 main/Main.scala diff --git a/launch/Boot.scala b/launch/Boot.scala index 741f14e3d..2cc17d56b 100644 --- a/launch/Boot.scala +++ b/launch/Boot.scala @@ -12,7 +12,7 @@ object Boot { System.clearProperty("scala.home") // avoid errors from mixing Scala versions in the same JVM CheckProxy() - try { Launch(args) } + try { Launch(args.toList) } catch { case b: BootException => errorAndExit(b) diff --git a/launch/Cache.scala b/launch/Cache.scala new file mode 100644 index 000000000..ed50dfbe8 --- /dev/null +++ b/launch/Cache.scala @@ -0,0 +1,19 @@ +package xsbt.boot + +import java.util.HashMap + +final class Cache[K,V <: AnyRef](create: K => V) extends NotNull +{ + private[this] val delegate = new HashMap[K,V] + def apply(k: K): V = + { + val existing = delegate.get(k) + if(existing eq null) newEntry(k) else existing + } + private[this] def newEntry(k: K): V = + { + val v = create(k) + delegate.put(k, v) + v + } +} \ No newline at end of file diff --git a/launch/CheckProxy.scala b/launch/CheckProxy.scala index dbd271173..a00c3b06c 100644 --- a/launch/CheckProxy.scala +++ b/launch/CheckProxy.scala @@ -3,6 +3,7 @@ */ package xsbt.boot +import Pre._ import java.net.{MalformedURLException, URL} object CheckProxy @@ -33,5 +34,5 @@ object CheckProxy private def copyEnv(envKey: String, sysKey: String) { setProperty(sysKey, System.getenv(envKey)) } private def setProperty(key: String, value: String) { if(value != null) System.setProperty(key, value) } private def isPropertyDefined(k: String) = isDefined(System.getProperty(k)) - private def isDefined(s: String) = s != null && !s.isEmpty + private def isDefined(s: String) = s != null && isNonEmpty(s) } \ No newline at end of file diff --git a/launch/Configuration.scala b/launch/Configuration.scala index dcf0afbb3..b683981f4 100644 --- a/launch/Configuration.scala +++ b/launch/Configuration.scala @@ -1,15 +1,16 @@ package xsbt.boot +import Pre._ 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).apply ) - def find(args: Seq[String], baseDirectory: File): (URL, Seq[String]) = + def find(args: List[String], baseDirectory: File): (URL, List[String]) = args match { - case Seq(head, tail @ _*) if head.startsWith("@")=> (configurationFromFile(head.substring(1), baseDirectory), tail) + case head :: tail if head.startsWith("@")=> (configurationFromFile(head.substring(1), baseDirectory), tail) case _ => val propertyConfigured = System.getProperty("sbt.boot.properties") val url = if(propertyConfigured == null) configurationOnClasspath else configurationFromFile(propertyConfigured, baseDirectory) @@ -17,7 +18,7 @@ object Configuration } def configurationOnClasspath: URL = { - resourcePaths.toStream.map(getClass.getResource).find(_ ne null) getOrElse + resourcePaths.elements.map(getClass.getResource).find(_ ne null) getOrElse ( multiPartError("Could not finder sbt launch configuration. Searched classpath for:", resourcePaths)) } def configurationFromFile(path: String, baseDirectory: File): URL = @@ -29,21 +30,23 @@ object Configuration if(exists) Some(resolved.toURL) else None } val against = resolveAgainst(baseDirectory) - against.toStream.flatMap(resolve).firstOption.getOrElse(multiPartError("Could not find configuration file '" + path + "'. Searched:", against)) + val resolving = against.elements.flatMap(e => resolve(e).toList.elements) + if(!resolving.hasNext) multiPartError("Could not find configuration file '" + path + "'. Searched:", against) + resolving.next() } - def multiPartError[T](firstLine: String, lines: Seq[T]) = throw new BootException( (Seq(firstLine) ++ lines).mkString("\n\t") ) + def multiPartError[T](firstLine: String, lines: List[T]) = error( (firstLine :: lines).mkString("\n\t") ) val ConfigurationName = "sbt.boot.properties" val JarBasePath = "/sbt/" def userConfigurationPath = "/" + ConfigurationName def defaultConfigurationPath = JarBasePath + ConfigurationName - def resourcePaths = Seq(userConfigurationPath, defaultConfigurationPath) - def resolveAgainst(baseDirectory: File) = Seq(baseDirectory toURI, new File(System.getProperty("user.home")) toURI, classLocation(getClass).toURI) + def resourcePaths = List(userConfigurationPath, defaultConfigurationPath) + def resolveAgainst(baseDirectory: File) = List(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) + if(codeSource == null) error("No class location for " + cl) else codeSource.getLocation } } \ No newline at end of file diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index c9eebd14c..f2a2be7ed 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -1,28 +1,26 @@ package xsbt.boot -import scala.util.parsing.combinator.Parsers -import scala.util.parsing.input.{Reader, StreamReader} - +import Pre._ import java.lang.Character.isWhitespace -import java.io.File +import java.io.{BufferedReader, File, FileInputStream, InputStreamReader, Reader, StringReader} import java.net.{MalformedURLException, URL} -import scala.collection.immutable.TreeMap +import java.util.regex.Pattern -class ConfigurationParser extends Parsers with NotNull +class ConfigurationParser extends NotNull { - 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 + def apply(file: File): LaunchConfiguration = Using(new InputStreamReader(new FileInputStream(file), "UTF-8"))(apply) + def apply(s: String): LaunchConfiguration = Using(new StringReader(s))(apply) + def apply(reader: Reader): LaunchConfiguration = Using(new BufferedReader(reader))(apply) + private def apply(in: BufferedReader): LaunchConfiguration = + { + def readLine(accum: List[Line], index: Int): List[Line] = { - case Success(r, _) => r - case _ => throw new BootException(r.toString) + val line = in.readLine() + if(line eq null) accum.reverse else readLine(ParseLine(line, index) ::: accum, index+1) } - + processSections(processLines(readLine(Nil, 0))) + } // section -> configuration instance processing - lazy val configuration = phrase(sections ^^ processSections) def processSections(sections: SectionMap): LaunchConfiguration = { val (scalaVersion, m1) = processSection(sections, "scala", getScalaVersion) @@ -39,32 +37,33 @@ class ConfigurationParser extends Parsers with NotNull def processVersion(value: Option[String]): Version = value.map(version).getOrElse(Version.default) def version(value: String): Version = { - if(value.isEmpty) throw new BootException("Version cannot be empty (omit version declaration to use the default version)") + if(isEmpty(value)) 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]) + Implicit(tokens(0), defaultVersion)(err => new Explicit(tokens(0))) } - 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 { throw new BootException(map.keys.mkString("Invalid " + label + "(s): ", ",","")) } - def check[T](label: String, pair: (T, Map[String, _])): T = { check(pair._2, label); pair._1 } + def processSection[T](sections: SectionMap, name: String, f: LabelMap => T) = + process[String,LabelMap,T](sections, name, m => f(m default(x => None))) + def process[K,V,T](sections: ListMap[K,V], name: K, f: V => T): (T, ListMap[K,V]) = ( f(sections(name)), sections - name) + def check(map: ListMap[String, _], label: String): Unit = if(map.isEmpty) () else error(map.keys.mkString("Invalid " + label + "(s): ", ",","")) + def check[T](label: String, pair: (T, ListMap[String, _])): T = { check(pair._2, label); pair._1 } def id(map: LabelMap, name: String, default: String): (String, LabelMap) = - (map.getOrElse(name, None).getOrElse(default), map - name) - def ids(map: LabelMap, name: String, default: Seq[String]) = + (getOrNone(map, name).getOrElse(default), map - name) + def getOrNone[K,V](map: ListMap[K,Option[V]], k: K) = map.get(k).getOrElse(None) + def ids(map: LabelMap, name: String, default: List[String]) = { - val result = map(name).map(value => trim(value.split(",")).filter(!_.isEmpty)).getOrElse(default) + val result = map(name).map(value => trim(value.split(",")).filter(isNonEmpty)).getOrElse(default) (result, map - name) } def bool(map: LabelMap, name: String, default: Boolean): (Boolean, LabelMap) = { val (b, m) = id(map, name, default.toString) - (b.toBoolean, m) + (toBoolean(b), m) } def toFile(path: String): File = new File(path)// if the path is relative, it will be resolve by Launch later def file(map: LabelMap, name: String, default: File): (File, LabelMap) = - (map.getOrElse(name, None).map(toFile).getOrElse(default), map - name) + (getOrNone(map, name).map(toFile).getOrElse(default), map - name) def getBoot(m: LabelMap): BootSetup = { @@ -78,13 +77,13 @@ class ConfigurationParser extends Parsers with NotNull BootSetup(dir, props, search, promptCreate, enableQuick, promptFill) } def getLogging(m: LabelMap): Logging = check("label", process(m, "level", getLevel)) - def getLevel(m: Option[String]) = m.map(LogLevel.apply).getOrElse(Logging(LogLevel.Info)) + def getLevel(m: Option[String]) = m.map(LogLevel.apply).getOrElse(new Logging(LogLevel.Info)) def getSearch(m: LabelMap, defaultPath: File): (Search, LabelMap) = ids(m, "search", Nil) match { case (Nil, newM) => (Search.none, newM) - case (Seq(tpe), newM) => (Search(tpe, Seq(defaultPath)), newM) - case (Seq(tpe, paths @ _ *), newM) => (Search(tpe, paths.map(toFile)), newM) + case (tpe :: Nil, newM) => (Search(tpe, List(defaultPath)), newM) + case (tpe :: paths, newM) => (Search(tpe, paths.map(toFile)), newM) } def getApplication(m: LabelMap): Application = @@ -93,125 +92,118 @@ class ConfigurationParser extends Parsers with NotNull 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 (components, m5) = ids(m4, "components", List("default")) val (crossVersioned, m6) = id(m5, "cross-versioned", "true") check(m6, "label") - new Application(org, name, rev, main, components, crossVersioned.toBoolean) + new Application(org, name, rev, main, components, toBoolean(crossVersioned)) } - def getRepositories(m: LabelMap): Seq[Repository] = + def getRepositories(m: LabelMap): List[Repository] = { import Repository.{Ivy, Maven, Predefined} - m.toSeq.map { + m.toList.map { case (key, None) => Predefined(key) case (key, Some(value)) => val r = trim(value.split(",",2)) - val url = try { new URL(r(0)) } catch { case e: MalformedURLException => throw new BootException("Invalid URL specified for '" + key + "': " + e.getMessage) } + val url = try { new URL(r(0)) } catch { case e: MalformedURLException => error("Invalid URL specified for '" + key + "': " + e.getMessage) } if(r.length == 2) Ivy(key, url, r(1)) else Maven(key, url) } } - def getAppProperties(m: LabelMap): Seq[AppProperty] = - for((name, Some(value)) <- m.toSeq) yield + def getAppProperties(m: LabelMap): List[AppProperty] = + for((name, Some(value)) <- m.toList) yield { - val map = Map() ++ trim(value.split(",")).map(parsePropertyDefinition(name)) + val map = ListMap( trim(value.split(",")).map(parsePropertyDefinition(name)) : _*) AppProperty(name)(map.get("quick"), map.get("new"), map.get("fill")) } def parsePropertyDefinition(name: String)(value: String) = value.split("=",2) match { case Array(mode,value) => (mode, parsePropertyValue(name, value)(defineProperty(name))) - case x => throw new BootException("Invalid property definition '" + x + "' for property '" + name + "'") + case x => error("Invalid property definition '" + x + "' for property '" + name + "'") } def defineProperty(name: String)(action: String, requiredArg: String, optionalArg: Option[String]) = action match { - case "prompt" => PromptProperty(requiredArg, optionalArg) - case "set" => SetProperty(requiredArg) - case _ => throw new BootException("Unknown action '" + action + "' for property '" + name + "'") + case "prompt" => new PromptProperty(requiredArg, optionalArg) + case "set" => new SetProperty(requiredArg) + case _ => error("Unknown action '" + action + "' for property '" + name + "'") } - private lazy val propertyPattern = """(.+)\((.*)\)(?:\[(.*)\])?""".r.pattern // examples: prompt(Version)[1.0] or set(1.0) + private lazy val propertyPattern = Pattern.compile("""(.+)\((.*)\)(?:\[(.*)\])?""") // 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) - if(!m.matches()) throw new BootException("Invalid property definition '" + definition + "' for property '" + name + "'") + if(!m.matches()) error("Invalid property definition '" + definition + "' for property '" + name + "'") 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) + def trim(s: Array[String]) = s.map(_.trim).toList - // line parsing - - def sections = lines ^^ processLines - type LabelMap = Map[String, Option[String]] + type LabelMap = ListMap[String, Option[String]] // section-name -> label -> value - type SectionMap = Map[String, LabelMap] + type SectionMap = ListMap[String, LabelMap] def processLines(lines: List[Line]): SectionMap = { type State = (SectionMap, Option[String]) val s: State = - ( ( (Map.empty withDefaultValue(TreeMap.empty[String,Option[String]]), None): State) /: lines ) { + ( ( (ListMap.empty.default(x => ListMap.empty[String,Option[String]]), None): State) /: lines ) { case (x, Comment) => x - case ( (map, _), Section(name) ) => (map, Some(name)) - case ( (_, None), l: Labeled ) => throw new BootException("Label " + l.label + " is not in a section") + case ( (map, _), s: Section ) => (map, Some(s.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) ) throw new BootException("Duplicate label '" + l.label + "' in section '" + 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 +final class Labeled(val label: String, val value: Option[String]) extends Line +final class Section(val name: String) extends Line object Comment extends Line + +class ParseException(val content: String, val line: Int, val col: Int, val msg: String) + extends BootException( "[" + (line+1) + ", " + (col+1) + "]" + msg + "\n" + content + "\n" + List.make(col," ").mkString + "^" ) + +object ParseLine +{ + def apply(content: String, line: Int) = + { + def error(col: Int, msg: String) = throw new ParseException(content, line, col, msg) + def check(condition: Boolean)(col: Int, msg: String) = if(condition) () else error(col, msg) + + val trimmed = trimLeading(content) + val offset = content.length - trimmed.length + + def section = + { + val closing = trimmed.indexOf(']', 1) + check(closing > 0)(content.length, "Expected ']', found end of line") + val extra = trimmed.substring(closing+1) + val trimmedExtra = trimLeading(extra) + check(isEmpty(trimmedExtra))(content.length - trimmedExtra.length, "Expected end of line, found '" + extra + "'") + new Section(trimmed.substring(1,closing).trim) + } + def labeled = + { + trimmed.split(":",2) match { + case Array(label, value) => + val trimmedValue = value.trim + check(isNonEmpty(trimmedValue))(content.indexOf(':'), "Value for '" + label + "' was empty") + new Labeled(label, Some(trimmedValue)) + case x => new Labeled(x.mkString, None) + } + } + + if(isEmpty(trimmed)) Nil + else + { + val processed = + trimmed.charAt(0) match + { + case '#' => Comment + case '[' => section + case _ => labeled + } + processed :: Nil + } + } +} \ No newline at end of file diff --git a/launch/Create.scala b/launch/Create.scala index 4d734defc..ec7ed1827 100644 --- a/launch/Create.scala +++ b/launch/Create.scala @@ -1,26 +1,27 @@ package xsbt.boot +import Pre._ import java.io.{File, FileInputStream, FileOutputStream} import java.util.Properties object Initialize { - def create(file: File, promptCreate: String, enableQuick: Boolean, spec: Seq[AppProperty]) + def create(file: File, promptCreate: String, enableQuick: Boolean, spec: List[AppProperty]) { SimpleReader.readLine(promptCreate + " (y/N" + (if(enableQuick) "/s" else "") + ") ") match { - case None => throw new BootException("") + case None => error("") case Some(line) => line.toLowerCase match { case "y" | "yes" => process(file, spec, _.create) - case "n" | "no" | "" => throw new BootException("") + case "n" | "no" | "" => error("") case "s" => process(file, spec, _.quick) } } } - def fill(file: File, spec: Seq[AppProperty]): Unit = process(file, spec, _.fill) - def process(file: File, appProperties: Seq[AppProperty], select: AppProperty => Option[PropertyInit]) + def fill(file: File, spec: List[AppProperty]): Unit = process(file, spec, _.fill) + def process(file: File, appProperties: List[AppProperty], select: AppProperty => Option[PropertyInit]) { val properties = new Properties if(file.exists) @@ -34,14 +35,14 @@ object Initialize { init match { - case SetProperty(value) => properties.setProperty(name, value) - case PromptProperty(label, default) => - def noValue = throw new BootException("No value provided for " + label) - SimpleReader.readLine(label + default.toList.map(" [" + _ + "]").mkString + ": ") match + case set: SetProperty => properties.setProperty(name, set.value) + case prompt: PromptProperty => + def noValue = error("No value provided for " + prompt.label) + SimpleReader.readLine(prompt.label + prompt.default.toList.map(" [" + _ + "]").mkString + ": ") match { case None => noValue case Some(line) => - val value = if(line.isEmpty) default.getOrElse(noValue) else line + val value = if(isEmpty(line)) prompt.default.getOrElse(noValue) else line properties.setProperty(name, value) } } diff --git a/launch/Enumeration.scala b/launch/Enumeration.scala new file mode 100644 index 000000000..382581295 --- /dev/null +++ b/launch/Enumeration.scala @@ -0,0 +1,29 @@ +/* sbt -- Simple Build Tool + * Copyright 2008,2009 David MacIver, Mark Harrah + */ +package xsbt.boot + +import Pre._ + +class Enumeration extends NotNull +{ + def elements: List[Value] = members + private lazy val members: List[Value] = + { + val c = getClass + val correspondingFields = ListMap( c.getDeclaredFields.map(f => (f.getName, f)) : _*) + c.getMethods.toList flatMap { method => + if(method.getParameterTypes.length == 0 && classOf[Value].isAssignableFrom(method.getReturnType)) + { + for(field <- correspondingFields.get(method.getName) if field.getType == method.getReturnType) yield + method.invoke(this).asInstanceOf[Value] + } + else + Nil + } + } + def value(s: String) = new Value(s) + class Value(override val toString: String) extends NotNull + + def toValue(s: String): Value = elements.find(_.toString == s).getOrElse(error("Expected one of " + elements.mkString(",") + " (got: " + s + ")")) +} \ No newline at end of file diff --git a/launch/Find.scala b/launch/Find.scala index d38f348e2..f4e78d22b 100644 --- a/launch/Find.scala +++ b/launch/Find.scala @@ -1,5 +1,6 @@ package xsbt.boot +import Pre._ import java.io.File import java.net.URI diff --git a/launch/Launch.scala b/launch/Launch.scala index f4526b2ef..5830b53a2 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -1,64 +1,79 @@ package xsbt.boot +import Pre._ import java.io.File import java.net.URL object Launch { - def apply(arguments: Seq[String]): Unit = apply( (new File("")).getAbsoluteFile , arguments ) + val start = System.currentTimeMillis + def time(label: String) = System.out.println(label + " : " + (System.currentTimeMillis - start) / 1000.0 + " s") + def apply(arguments: List[String]): Unit = apply( (new File("")).getAbsoluteFile , arguments ) - def apply(currentDirectory: File, arguments: Seq[String]): Unit = + def apply(currentDirectory: File, arguments: List[String]): Unit = Configuration.find(arguments, currentDirectory) match { case (configLocation, newArguments) => configured(currentDirectory, configLocation, newArguments) } - def configured(currentDirectory: File, configLocation: URL, arguments: Seq[String]): Unit = + def configured(currentDirectory: File, configLocation: URL, arguments: List[String]): Unit = { + time("found boot config") val config = Configuration.parse(configLocation, currentDirectory) + time("parsed") Find(config, currentDirectory) match { case (resolved, baseDirectory) => parsed(baseDirectory, resolved, arguments) } } - def parsed(currentDirectory: File, parsed: LaunchConfiguration, arguments: Seq[String]): Unit = + def parsed(currentDirectory: File, parsed: LaunchConfiguration, arguments: List[String]): Unit = { + time("found working directory") val propertiesFile = parsed.boot.properties import parsed.boot.{enableQuick, promptCreate, promptFill} - if(!promptCreate.isEmpty && !propertiesFile.exists) + if(isNonEmpty(promptCreate) && !propertiesFile.exists) Initialize.create(propertiesFile, promptCreate, enableQuick, parsed.appProperties) else if(promptFill) Initialize.fill(propertiesFile, parsed.appProperties) + time("initialized") initialized(currentDirectory, parsed, arguments) } - def initialized(currentDirectory: File, parsed: LaunchConfiguration, arguments: Seq[String]): Unit = - explicit(currentDirectory, ResolveVersions(parsed), arguments) + def initialized(currentDirectory: File, parsed: LaunchConfiguration, arguments: List[String]): Unit = + { + val resolved = ResolveVersions(parsed) + time("resolved") + explicit(currentDirectory, resolved, arguments) + } - def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: Seq[String]): Unit = + def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: List[String]): Unit = launch( run(new Launch(explicit.boot.directory, explicit.repositories)) ) ( - RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments) ) + new RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments) ) def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult = { import config._ val scalaProvider: xsbti.ScalaProvider = launcher.getScala(scalaVersion) val appProvider: xsbti.AppProvider = scalaProvider.app(app) - val appConfig: xsbti.AppConfiguration = new AppConfiguration(arguments.toArray, workingDirectory, appProvider) + val appConfig: xsbti.AppConfiguration = new AppConfiguration(toArray(arguments), workingDirectory, appProvider) - appProvider.newMain().run(appConfig) + time("pre-load") + val main = appProvider.newMain() + time("loaded") + val result = main.run(appConfig) + time("ran") + result } 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)(new RunConfiguration(r.scalaVersion, r.app, r.baseDirectory, r.arguments.toList)) 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]) extends NotNull +final class RunConfiguration(val scalaVersion: String, val app: xsbti.ApplicationID, val workingDirectory: File, val arguments: List[String]) extends NotNull import BootConfiguration.{appDirectoryName, baseDirectoryName, ScalaDirectoryName, TestLoadScalaClasses} -class Launch(val bootDirectory: File, repositories: Seq[Repository]) extends xsbti.Launcher +class Launch(val bootDirectory: File, repositories: List[Repository]) 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)) + private val scalaProviders = new Cache[String, ScalaProvider](new ScalaProvider(_)) + def getScala(version: String): xsbti.ScalaProvider = scalaProviders(version) lazy val topLoader = new BootFilteredLoader(getClass.getClassLoader) @@ -74,7 +89,7 @@ class Launch(val bootDirectory: File, repositories: Seq[Repository]) extends xsb lazy val scalaHome = new File(libDirectory, ScalaDirectoryName) def compilerJar = new File(scalaHome, "scala-compiler.jar") def libraryJar = new File(scalaHome, "scala-library.jar") - def baseDirectories = Seq(scalaHome) + def baseDirectories = List(scalaHome) def testLoadClasses = TestLoadScalaClasses def target = UpdateScala def failLabel = "Scala " + version @@ -87,8 +102,8 @@ class Launch(val bootDirectory: File, repositories: Seq[Repository]) extends xsb def configuration = ScalaProvider.this.configuration lazy val appHome = new File(libDirectory, appDirectoryName(id, File.separator)) def parentLoader = ScalaProvider.this.loader - def baseDirectories = id.mainComponents.map(components.componentLocation) ++ Seq(appHome) - def testLoadClasses = Seq(id.mainClass) + def baseDirectories = appHome :: id.mainComponents.map(components.componentLocation).toList + def testLoadClasses = List(id.mainClass) def target = new UpdateApp(Application(id)) def failLabel = id.name + " " + id.version @@ -113,7 +128,7 @@ class ComponentProvider(baseDirectory: File) extends xsbti.ComponentProvider if(location.exists) throw new BootException("Cannot redefine component. ID: " + id + ", files: " + files.mkString(",")) else - Copy(files, location) + Copy(files.toList, location) } def lockFile = ComponentProvider.lockFile(baseDirectory) } diff --git a/launch/LaunchConfiguration.scala b/launch/LaunchConfiguration.scala index 151495c75..d833d83c3 100644 --- a/launch/LaunchConfiguration.scala +++ b/launch/LaunchConfiguration.scala @@ -1,47 +1,42 @@ package xsbt.boot +import Pre._ import java.io.File import java.net.URL -final case class LaunchConfiguration(scalaVersion: Version, app: Application, repositories: Seq[Repository], boot: BootSetup, logging: Logging, appProperties: Seq[AppProperty]) extends NotNull +final case class LaunchConfiguration(scalaVersion: Version, app: Application, repositories: List[Repository], boot: BootSetup, logging: Logging, appProperties: List[AppProperty]) extends NotNull { def getScalaVersion = Version.get(scalaVersion) - def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(Version.Explicit(newScalaVersion), app, repositories, boot, logging, appProperties) + def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), app, repositories, boot, logging, appProperties) def withApp(app: Application) = LaunchConfiguration(scalaVersion, app, repositories, boot, logging, appProperties) - def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) - def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(Version.Explicit(newScalaVersion), app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) + def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, app.withVersion(new Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) + def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), app.withVersion(new Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) def map(f: File => File) = LaunchConfiguration(scalaVersion, app, repositories, boot.map(f), logging, appProperties) } sealed trait Version extends NotNull object Version { - final case class Explicit(value: String) extends Version - final case class Implicit(default: Option[String]) extends Version + final class Explicit(val value: String) extends Version + final class Implicit(val default: Option[String]) extends Version { - require(default.isEmpty || !default.get.isEmpty, "Default cannot be empty") + require(default.isEmpty || isNonEmpty(default.get), "Default cannot be the empty string") } object Implicit { - def apply(s: String, default: Option[String]): Either[String, Implicit] = - if(s == "read") Right(Implicit(default)) else Left("Expected 'read', got '" + s +"'") + def apply(s: String, default: Option[String])(handleError: String => Version): Version = + if(s == "read") new Implicit(default) else handleError("Expected 'read', got '" + s +"'") } - def get(v: Version) = v match { case Version.Explicit(v) => v; case _ => throw new BootException("Unresolved version: " + v) } - def default = Implicit(None) + def get(v: Version) = v match { case e: Version.Explicit => e.value; case _ => throw new BootException("Unresolved version: " + v) } + def default = new Implicit(None) } -sealed abstract class RichEnum extends Enumeration -{ - def fromString(s: String): Either[String, Value] = elements.find(_.toString == s).toRight("Expected one of " + elements.mkString(",") + " (got: " + s + ")") - def toValue(s: String): Value = fromString(s) match { case Left(msg) => error(msg); case Right(t) => t } -} - -final case class Application(groupID: String, name: String, version: Version, main: String, components: Seq[String], crossVersioned: Boolean) extends NotNull +final case class Application(groupID: String, name: String, version: Version, main: String, components: List[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) + def toID = AppID(groupID, name, getVersion, main, toArray(components), crossVersioned) } final case class AppID(groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersioned: Boolean) extends xsbti.ApplicationID @@ -50,7 +45,7 @@ object Application def apply(id: xsbti.ApplicationID): Application = { import id._ - Application(groupID, name, Version.Explicit(version), mainClass, mainComponents, crossVersioned) + Application(groupID, name, new Version.Explicit(version), mainClass, mainComponents.toList, crossVersioned) } } @@ -61,28 +56,28 @@ object Repository final case class Ivy(id: String, url: URL, pattern: String) extends Repository final case class Predefined(id: Predefined.Value) extends Repository - object Predefined extends RichEnum + object Predefined extends Enumeration { - 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-snapshots") + 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-snapshots") def apply(s: String): Predefined = Predefined(toValue(s)) } - def defaults: Seq[Repository] = Predefined.elements.map(Predefined.apply).toList + def defaults: List[Repository] = Predefined.elements.map(Predefined.apply).toList } -final case class Search(tpe: Search.Value, paths: Seq[File]) extends NotNull -object Search extends RichEnum +final case class Search(tpe: Search.Value, paths: List[File]) extends NotNull +object Search extends Enumeration { def none = Search(Current, Nil) - val Only = Value("only") - val RootFirst = Value("root-first") - val Nearest = Value("nearest") - val Current = Value("none") - def apply(s: String, paths: Seq[File]): Search = Search(toValue(s), paths) + val Only = value("only") + val RootFirst = value("root-first") + val Nearest = value("nearest") + val Current = value("none") + def apply(s: String, paths: List[File]): Search = Search(toValue(s), paths) } final case class BootSetup(directory: File, properties: File, search: Search, promptCreate: String, enableQuick: Boolean, promptFill: Boolean) extends NotNull @@ -92,21 +87,21 @@ final case class BootSetup(directory: File, properties: File, search: Search, pr final case class AppProperty(name: String)(val quick: Option[PropertyInit], val create: Option[PropertyInit], val fill: Option[PropertyInit]) extends NotNull sealed trait PropertyInit extends NotNull -final case class SetProperty(value: String) extends PropertyInit -final case class PromptProperty(label: String, default: Option[String]) extends PropertyInit +final class SetProperty(val value: String) extends PropertyInit +final class PromptProperty(val label: String, val default: Option[String]) extends PropertyInit -final case class Logging(level: LogLevel.Value) extends NotNull -object LogLevel extends RichEnum +final class Logging(level: LogLevel.Value) extends NotNull +object LogLevel extends Enumeration { - val Debug = Value("debug") - val Info = Value("info") - val Warn = Value("warn") - val Error = Value("error") - def apply(s: String): Logging = Logging(toValue(s)) + val Debug = value("debug") + val Info = value("info") + val Warn = value("warn") + val Error = value("error") + def apply(s: String): Logging = new Logging(toValue(s)) } final 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. -final class BootException(override val toString: String) extends RuntimeException \ No newline at end of file +class BootException(override val toString: String) extends RuntimeException \ No newline at end of file diff --git a/launch/ListMap.scala b/launch/ListMap.scala new file mode 100644 index 000000000..a59869175 --- /dev/null +++ b/launch/ListMap.scala @@ -0,0 +1,32 @@ +package xsbt.boot + +import Pre._ + +// preserves iteration order +sealed class ListMap[K,V] private(backing: List[(K,V)]) extends Iterable[(K,V)] with NotNull +{ + import ListMap.remove + def update(k: K, v: V) = this.+( (k,v) ) + def +(pair: (K,V)) = copy(pair :: remove(backing,pair._1)) + def -(k: K) = copy(remove(backing,k)) + def get(k: K): Option[V] = backing.find(_._1 == k).map(_._2) + def keys: List[K] = backing.reverse.map(_._1) + def apply(k: K): V = get(k).getOrElse(error("Key " + k + " not found")) + def contains(k: K): Boolean = get(k).isDefined + def elements: Iterator[(K,V)] = backing.reverse.elements + override def isEmpty: Boolean = backing.isEmpty + override def toList = backing.reverse + override def toSeq = toList + protected def copy(newBacking: List[(K,V)]): ListMap[K,V] = new ListMap(newBacking) + def default(defaultF: K => V): ListMap[K,V] = + new ListMap[K,V](backing) { + override def apply(k: K) = super.get(k).getOrElse(defaultF(k)) + override def copy(newBacking: List[(K,V)]) = super.copy(newBacking).default(defaultF) + } +} +object ListMap +{ + def apply[K,V](pairs: (K,V)*) = (empty[K,V] /: pairs)(_ + _) + def empty[K,V] = new ListMap[K,V](Nil) + private def remove[K,V](backing: List[(K,V)], k: K) = backing.filter(_._1 != k) +} diff --git a/launch/Locks.scala b/launch/Locks.scala index 99c996c35..10476121c 100644 --- a/launch/Locks.scala +++ b/launch/Locks.scala @@ -7,13 +7,9 @@ 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] + private[this] val locks = new Cache[File, GlobalLock](new GlobalLock(_)) def apply[T](file: File, action: Callable[T]) = - { - val canonFile = file.getCanonicalFile - synchronized { locks.getOrElseUpdate(canonFile, new GlobalLock(canonFile)).withLock(action) } - } + synchronized { locks(file.getCanonicalFile).withLock(action) } private[this] class GlobalLock(file: File) { @@ -37,7 +33,7 @@ object Locks extends xsbti.GlobalLock val freeLock = channel.tryLock if(freeLock eq null) { - println("Waiting for lock on " + file + " to be available..."); + System.out.println("Waiting for lock on " + file + " to be available..."); val lock = channel.lock try { run.call } finally { lock.release() } diff --git a/launch/Pre.scala b/launch/Pre.scala new file mode 100644 index 000000000..703bf109c --- /dev/null +++ b/launch/Pre.scala @@ -0,0 +1,29 @@ +package xsbt.boot + +object Pre +{ + def trimLeading(line: String) = + { + def newStart(i: Int): Int = if(i >= line.length || !Character.isWhitespace(line.charAt(i))) i else newStart(i+1) + line.substring(newStart(0)) + } + def isEmpty(line: String) = line.length == 0 + def isNonEmpty(line: String) = line.length > 0 + def assert(condition: Boolean, msg: => String): Unit = if (!condition) throw new AssertionError(msg) + def assert(condition: Boolean): Unit = assert(condition, "Assertion failed") + def require(condition: Boolean, msg: => String): Unit = if (!condition) throw new IllegalArgumentException(msg) + def error(msg: String): Nothing = throw new BootException(msg) + def toBoolean(s: String) = java.lang.Boolean.parseBoolean(s) + def toArray[T](list: List[T]) = + { + val arr = new Array[T](list.length) + def copy(i: Int, rem: List[T]): Unit = + if(i < arr.length) + { + arr(i) = rem.head + copy(i+1, rem.tail) + } + copy(0, list) + arr + } +} diff --git a/launch/Provider.scala b/launch/Provider.scala index 0b8e3c3f1..00b1c9bf2 100644 --- a/launch/Provider.scala +++ b/launch/Provider.scala @@ -1,13 +1,14 @@ package xsbt.boot +import Pre._ import java.io.{File, FileFilter} import java.net.{URL, URLClassLoader} trait Provider extends NotNull { def configuration: UpdateConfiguration - def baseDirectories: Seq[File] - def testLoadClasses: Seq[String] + def baseDirectories: List[File] + def testLoadClasses: List[String] def target: UpdateTarget def failLabel: String def parentLoader: ClassLoader @@ -28,13 +29,13 @@ trait Provider extends NotNull def createLoader = { val jars = GetJars(baseDirectories) - (jars, new URLClassLoader(jars.map(_.toURI.toURL).toArray, parentLoader) ) + (jars, new URLClassLoader(jars.map(_.toURI.toURL), parentLoader) ) } } object GetJars { - def apply(directories: Seq[File]): Array[File] = directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter))).toArray + def apply(directories: List[File]): Array[File] = toArray(directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter)))) def wrapNull(a: Array[File]): Array[File] = if(a == null) Array() else a object JarFilter extends FileFilter @@ -45,12 +46,12 @@ object GetJars 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(", "))) + checkTarget(loader, classes, (), missing => error("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) + val missing = classes.toList.filter(classMissing) if(missing.isEmpty) ifSuccess else ifFailure(missing) } } \ No newline at end of file diff --git a/launch/ResolveVersions.scala b/launch/ResolveVersions.scala index e314cba41..c59828fcc 100644 --- a/launch/ResolveVersions.scala +++ b/launch/ResolveVersions.scala @@ -1,15 +1,14 @@ package xsbt.boot +import Pre._ import java.io.{File, FileInputStream} import java.util.Properties -final case class ResolvedVersion(v: String, wasExplicit: Boolean) extends NotNull - object ResolveVersions { 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 notEmpty(s: String) = if(isEmpty(s)) None else Some(s) private def readProperties(propertiesFile: File) = { val properties = new Properties @@ -40,7 +39,7 @@ final class ResolveVersions(conf: LaunchConfiguration) extends NotNull v match { case e: Version.Explicit => e.value - case Version.Implicit(default) => readVersion() orElse default getOrElse noVersionInFile + case i: Version.Implicit => readVersion() orElse i.default getOrElse noVersionInFile } } def readVersion() = trim(properties.getProperty(versionProperty)) diff --git a/launch/Update.scala b/launch/Update.scala index 36d3665b5..b87e77a3b 100644 --- a/launch/Update.scala +++ b/launch/Update.scala @@ -3,7 +3,9 @@ */ package xsbt.boot +import Pre._ import java.io.{File, FileWriter, PrintWriter, Writer} +import java.util.regex.Pattern import org.apache.ivy.{core, plugins, util, Ivy} import core.LogOptions @@ -23,9 +25,9 @@ import BootConfiguration._ sealed trait UpdateTarget extends NotNull { def tpe: String } final object UpdateScala extends UpdateTarget { def tpe = "scala" } -final case class UpdateApp(id: Application) extends UpdateTarget { def tpe = "app" } +final class UpdateApp(val id: Application) extends UpdateTarget { def tpe = "app" } -final class UpdateConfiguration(val bootDirectory: File, val scalaVersion: String, val repositories: Seq[Repository]) extends NotNull +final class UpdateConfiguration(val bootDirectory: File, val scalaVersion: String, val repositories: List[Repository]) extends NotNull /** Ensures that the Scala and application jars exist for the given versions or else downloads them.*/ final class Update(config: UpdateConfiguration) @@ -58,7 +60,7 @@ final class Update(config: UpdateConfiguration) case e: Exception => e.printStackTrace(logWriter) log(e.toString) - println(" (see " + logFile + " for complete log)") + System.out.println(" (see " + logFile + " for complete log)") } finally { @@ -80,7 +82,8 @@ final class Update(config: UpdateConfiguration) case UpdateScala => addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default") addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default") - case UpdateApp(app) => + case u: UpdateApp => + val app = u.id val resolvedName = if(app.crossVersioned) app.name + "_" + scalaVersion else app.name addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)") } @@ -112,8 +115,10 @@ final class Update(config: UpdateConfiguration) if(resolveReport.hasError) { logExceptions(resolveReport) - println(Set(resolveReport.getAllProblemMessages.toArray: _*).mkString(System.getProperty("line.separator"))) - throw new BootException("Error retrieving required libraries") + val seen = new java.util.LinkedHashSet[Any] + seen.addAll(resolveReport.getAllProblemMessages) + System.out.println(seen.toArray.mkString(System.getProperty("line.separator"))) + error("Error retrieving required libraries") } } /** Exceptions are logged to the update log file. */ @@ -135,7 +140,7 @@ final class Update(config: UpdateConfiguration) target match { case UpdateScala => scalaRetrievePattern - case UpdateApp(app) => appRetrievePattern(app.toID) + case u: UpdateApp => appRetrievePattern(u.id.toID) } retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaVersion) + "/" + pattern, retrieveOptions) } @@ -144,7 +149,7 @@ final class Update(config: UpdateConfiguration) { val newDefault = new ChainResolver newDefault.setName("redefined-public") - if(repositories.isEmpty) throw new BootException("No repositories defined.") + if(repositories.isEmpty) error("No repositories defined.") repositories.foreach(repo => newDefault.add(toIvyRepository(settings, repo))) onDefaultRepositoryCacheManager(settings)(_.setUseOrigin(true)) settings.addResolver(newDefault) @@ -210,13 +215,13 @@ final class Update(config: UpdateConfiguration) resolver.addArtifactPattern(localIvyRoot + "/" + LocalArtifactPattern) resolver } - private val SnapshotPattern = """(\d+).(\d+).(\d+)-(\d{8})\.(\d{6})-(\d+|\+)""".r.pattern + private val SnapshotPattern = Pattern.compile("""(\d+).(\d+).(\d+)-(\d{8})\.(\d{6})-(\d+|\+)""") private def scalaSnapshots(scalaVersion: String) = { val m = SnapshotPattern.matcher(scalaVersion) if(m.matches) { - val base = Seq(1,2,3).map(m.group).mkString(".") + val base = List(1,2,3).map(m.group).mkString(".") val pattern = "http://scala-tools.org/repo-snapshots/[organization]/[module]/" + base + "-SNAPSHOT/[artifact]-[revision].[ext]" val resolver = new URLResolver @@ -233,7 +238,7 @@ final class Update(config: UpdateConfiguration) { try { logWriter.println(msg) } catch { case e: Exception => System.err.println("Error writing to update log file: " + e.toString) } - println(msg) + System.out.println(msg) } } /** A custom logger for Ivy to ignore the messages about not finding classes diff --git a/launch/Using.scala b/launch/Using.scala index dc66db93f..0974c8032 100644 --- a/launch/Using.scala +++ b/launch/Using.scala @@ -10,7 +10,7 @@ object Using extends NotNull object Copy { - def apply(files: Seq[File], toDirectory: File): Unit = files.foreach(file => apply(file, toDirectory)) + def apply(files: List[File], toDirectory: File): Unit = files.foreach(file => apply(file, toDirectory)) def apply(file: File, toDirectory: File) { toDirectory.mkdirs() diff --git a/launch/src/test/scala/ScalaProviderTest.scala b/launch/src/test/scala/ScalaProviderTest.scala index d2adb35d9..b324d7f39 100644 --- a/launch/src/test/scala/ScalaProviderTest.scala +++ b/launch/src/test/scala/ScalaProviderTest.scala @@ -20,19 +20,19 @@ object ScalaProviderTest extends Specification "Launch" should { "Successfully load an application from local repository and run it with correct arguments" in { - checkLoad(Array("test"), "xsbt.boot.test.ArgumentTest").asInstanceOf[Exit].code must be(0) - checkLoad(Array(), "xsbt.boot.test.ArgumentTest") must throwA[RuntimeException] + checkLoad(List("test"), "xsbt.boot.test.ArgumentTest").asInstanceOf[Exit].code must be(0) + checkLoad(List(), "xsbt.boot.test.ArgumentTest") must throwA[RuntimeException] } "Successfully load an application from local repository and run it with correct sbt version" in { - checkLoad(Array(), "xsbt.boot.test.AppVersionTest").asInstanceOf[Exit].code must be(0) + checkLoad(List(), "xsbt.boot.test.AppVersionTest").asInstanceOf[Exit].code must be(0) } } - private def checkLoad(arguments: Array[String], mainClassName: String): MainResult = + private def checkLoad(arguments: List[String], mainClassName: String): MainResult = FileUtilities.withTemporaryDirectory { currentDirectory => withLauncher { launcher => Launch.run(launcher)( - RunConfiguration(mapScalaVersion(LaunchTest.getScalaVersion), LaunchTest.testApp(mainClassName).toID, currentDirectory, arguments) + new RunConfiguration(mapScalaVersion(LaunchTest.getScalaVersion), LaunchTest.testApp(mainClassName).toID, currentDirectory, arguments) ) } } @@ -49,9 +49,9 @@ object ScalaProviderTest extends Specification } object LaunchTest { - def testApp(main: String) = Application("org.scala-tools.sbt", "launch-test", Version.Explicit(test.MainTest.Version), main, Nil, false) + def testApp(main: String) = Application("org.scala-tools.sbt", "launch-test", new Version.Explicit(test.MainTest.Version), main, Nil, false) import Repository.Predefined._ - def testRepositories = Seq(Local, ScalaToolsReleases, ScalaToolsSnapshots).map(Repository.Predefined.apply) + def testRepositories = List(Local, ScalaToolsReleases, ScalaToolsSnapshots).map(Repository.Predefined.apply) def withLauncher[T](f: xsbti.Launcher => T): T = FileUtilities.withTemporaryDirectory { bootDirectory => f(new Launch(bootDirectory, testRepositories)) @@ -59,7 +59,7 @@ object LaunchTest def mapScalaVersion(versionNumber: String) = scalaVersionMap.find(_._2 == versionNumber).getOrElse { error("Scala version number " + versionNumber + " from library.properties has no mapping")}._1 - val scalaVersionMap = Map("2.7.2" -> "2.7.2") ++ Seq("2.7.3", "2.7.4", "2.7.5").map(v => (v, v + ".final")) + val scalaVersionMap = Map( ("2.7.2", "2.7.2") ) ++ List("2.7.3", "2.7.4", "2.7.5").map(v => (v, v + ".final")) def getScalaVersion: String = getScalaVersion(getClass.getClassLoader) def getScalaVersion(loader: ClassLoader): String = { diff --git a/main/Main.scala b/main/Main.scala deleted file mode 100644 index bc7a5be3e..000000000 --- a/main/Main.scala +++ /dev/null @@ -1,12 +0,0 @@ -package xsbt - -class Main extends xsbti.AppMain -{ - def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = - { - println(configuration.arguments.mkString("\n")) - Exit(0) - } -} - -final case class Exit(code: Int) extends xsbti.Exit \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index 813477920..d69084ecb 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,5 +1,5 @@ #Project properties -#Sun Sep 13 00:00:33 EDT 2009 +#Wed Oct 07 13:14:09 EDT 2009 project.organization=org.scala-tools.sbt project.name=xsbt sbt.version=0.5.5 diff --git a/project/build/LauncherProguard.scala b/project/build/LauncherProguard.scala index 0216fe064..58bb1a325 100644 --- a/project/build/LauncherProguard.scala +++ b/project/build/LauncherProguard.scala @@ -3,7 +3,6 @@ import java.io.File trait ProguardLaunch extends ProguardProject { - override def basicOptions = super.basicOptions ++ Seq(keepJLine) def outputJar = rootProject.outputPath / ("xsbt-launch-" + version + ".jar") override def keepClasses = "org.apache.ivy.plugins.resolver.URLResolver" :: @@ -21,11 +20,25 @@ trait ProguardLaunch extends ProguardProject log.debug("proguard configuration ivy jar location: " + ivyJars.mkString(", ")) - ((withJar(ivyJars.toSeq, "Ivy") + "(!META-INF/**,!fr/**,!**/antlib.xml,!**/*.png)") :: + val excludeIvyResourcesString = excludeString(excludeIvyResources) + ((withJar(ivyJars.toSeq, "Ivy") + excludeIvyResourcesString) :: (withJar(jlineJars, "JLine") + "(!META-INF/**)" ) :: otherJars.map(jar => mkpath(jar) + "(!META-INF/**,!*.properties)").toList) map { "-injars " + _ } } + private def excludeString(s: List[String]) = s.map("!" + _).mkString("(",",",")") + private def excludeIvyResources = + "META-INF/**" :: + "fr/**" :: + "**/antlib.xml" :: + "**/*.png" :: + "org/apache/ivy/core/settings/ivyconf*.xml" :: + "org/apache/ivy/core/settings/ivysettings-*.xml" :: + "org/apache/ivy/plugins/resolver/packager/*" :: + "**/ivy_vfs.xml" :: + "org/apache/ivy/plugins/report/ivy-report-*" :: + Nil + private def withJar[T](files: Seq[File], name: String) = mkpath(files.firstOption.getOrElse(error(name + " not present (try running update)"))) private def isJLineJar(file: File) = isJarX(file, "jline") private def isJarX(file: File, x: String) = diff --git a/project/build/ProguardProject.scala b/project/build/ProguardProject.scala index 93be136a5..d16d2985c 100644 --- a/project/build/ProguardProject.scala +++ b/project/build/ProguardProject.scala @@ -36,6 +36,7 @@ trait ProguardProject extends BasicScalaProject def mapInJars(inJars: Seq[File]): Seq[String] = inJars.map(f => "-injars " + mkpath(f)) def mapLibraryJars(libraryJars: Seq[File]): Seq[String] = libraryJars.map(f => "-libraryjars " + mkpath(f)) + def mapOutJar(outJar: File) = "-outjars " + mkpath(outJar) def template(inJars: Seq[File], libraryJars: Seq[File], outJar: File, options: Seq[String], mainClass: Option[String], keepClasses: Seq[String]) = { @@ -49,7 +50,7 @@ trait ProguardProject extends BasicScalaProject keepClasses.map("-keep public class " + _ + " {\n public * ;\n}") ++ mapInJars(inJars) ++ Seq("-injars " + mkpath(rawJarPath.asFile), - "-outjars " + mkpath(outJar)) ++ + mapOutJar(outJar)) ++ mapLibraryJars(libraryJars) ++ mainClass.map(main => keepMain.stripMargin.format(main)).toList lines.mkString("\n") diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 78c763e22..236485965 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -26,7 +26,6 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub) val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, compilerSub) - val mainSub = project("main", "Main", new Base(_), stdTaskSub) val distSub = project("dist", "Distribution", new DistProject(_)) /* Multi-subproject paths */