General support for searching for the working directory to use for the launched application

This commit is contained in:
Mark Harrah 2009-10-01 22:59:02 -04:00
parent 859767f0e1
commit 34fd967c66
8 changed files with 131 additions and 41 deletions

View File

@ -35,9 +35,4 @@ class AnalyzingCompiler(scalaInstance: ScalaInstance, manager: ComponentManager)
new DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false) new DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false)
} }
override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")" override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")"
}/* }
object AnalyzingCompiler
{
def apply(scalaVersion: String, provider: xsbti.ScalaProvider, manager: ComponentManager): AnalyzingCompiler =
new AnalyzingCompiler(ScalaInstance(scalaVersion, provider), manager)
}*/

View File

@ -10,7 +10,7 @@ object Boot
{ {
def main(args: Array[String]) def main(args: Array[String])
{ {
System.setProperty("scala.home", "") // avoid errors from mixing Scala versions in the same JVM System.clearProperty("scala.home") // avoid errors from mixing Scala versions in the same JVM
CheckProxy() CheckProxy()
try { Launch(args) } try { Launch(args) }
catch catch

View File

@ -5,7 +5,7 @@ import java.net.{URI, URL}
object Configuration object Configuration
{ {
def parse(file: URL, baseDirectory: File) = Using( new InputStreamReader(file.openStream, "utf8") )( (new ConfigurationParser(baseDirectory)).apply ) 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: Seq[String], baseDirectory: File): (URL, Seq[String]) =
args match args match
{ {

View File

@ -5,10 +5,10 @@ import scala.util.parsing.input.{Reader, StreamReader}
import java.lang.Character.isWhitespace import java.lang.Character.isWhitespace
import java.io.File import java.io.File
import java.net.{MalformedURLException, URL}
class ConfigurationParser(workingDirectory: File) extends Parsers with NotNull class ConfigurationParser extends Parsers with NotNull
{ {
def this() = this(new File("."))
import scala.util.parsing.input.CharSequenceReader import scala.util.parsing.input.CharSequenceReader
def apply(s: String): LaunchConfiguration = processResult(configuration(new CharSequenceReader(s, 0))) def apply(s: String): LaunchConfiguration = processResult(configuration(new CharSequenceReader(s, 0)))
def apply(source: java.io.Reader): LaunchConfiguration = processResult(configuration(StreamReader(source))) def apply(source: java.io.Reader): LaunchConfiguration = processResult(configuration(StreamReader(source)))
@ -55,18 +55,27 @@ class ConfigurationParser(workingDirectory: File) extends Parsers with NotNull
val result = map(name).map(value => trim(value.split(",")).filter(!_.isEmpty)).getOrElse(default) val result = map(name).map(value => trim(value.split(",")).filter(!_.isEmpty)).getOrElse(default)
(result, map - name) (result, map - name)
} }
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) = def file(map: LabelMap, name: String, default: File): (File, LabelMap) =
(map.getOrElse(name, None).map(p => new File(p)).getOrElse(default), map - name) (map.getOrElse(name, None).map(toFile).getOrElse(default), map - name)
def getBoot(m: LabelMap): BootSetup = def getBoot(m: LabelMap): BootSetup =
{ {
val (dir, m1) = file(m, "directory", new File(workingDirectory, "project/boot")) val (dir, m1) = file(m, "directory", toFile("project/boot"))
val (props, m2) = file(m1, "properties", new File("project/build.properties")) val (props, m2) = file(m1, "properties", toFile("project/build.properties"))
check(m2, "label") val (search, m3) = getSearch(m2, props)
BootSetup(dir, props) check(m3, "label")
BootSetup(dir, props, search)
} }
def getLogging(m: LabelMap): Logging = check("label", process(m, "level", getLevel)) def getLogging(m: LabelMap): Logging = check("label", process(m, "level", getLevel))
def getLevel(m: Option[String]) = m.map(l => LogLevel(l).fold(error, identity[Logging]) ).getOrElse(Logging(LogLevel.Info)) def getLevel(m: Option[String]) = m.map(LogLevel.apply).getOrElse(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)
}
def getApplication(m: LabelMap): Application = def getApplication(m: LabelMap): Application =
{ {
@ -83,11 +92,10 @@ class ConfigurationParser(workingDirectory: File) extends Parsers with NotNull
{ {
import Repository.{Ivy, Maven, Predefined} import Repository.{Ivy, Maven, Predefined}
m.toSeq.map { m.toSeq.map {
case (key, None) => Predefined(key).fold(err => error(err), x => x) case (key, None) => Predefined(key)
case (key, Some(value)) => case (key, Some(value)) =>
val r = trim(value.split(",",2)) val r = trim(value.split(",",2))
val url = r(0) val url = try { new URL(r(0)) } catch { case e: MalformedURLException => error("Invalid URL specified for '" + key + "': " + e.getMessage) }
if(url.isEmpty) error("No URL specified for '" + key + "'")
if(r.length == 2) Ivy(key, url, r(1)) else Maven(key, url) if(r.length == 2) Ivy(key, url, r(1)) else Maven(key, url)
} }
} }

48
launch/Find.scala Normal file
View File

@ -0,0 +1,48 @@
package xsbt.boot
import java.io.File
import java.net.URI
object Find { def apply(config: LaunchConfiguration, currentDirectory: File) = (new Find(config))(currentDirectory) }
class Find(config: LaunchConfiguration) extends NotNull
{
import config.boot.search
def apply(currentDirectory: File) =
{
val current = currentDirectory.getCanonicalFile
assert(current.isDirectory)
lazy val fromRoot = path(current, Nil).filter(hasProject).map(_.getCanonicalFile)
val found: Option[File] =
search.tpe match
{
case Search.RootFirst => fromRoot.headOption
case Search.Nearest => fromRoot.lastOption
case Search.Only =>
if(hasProject(current))
Some(current)
else
fromRoot match
{
case Nil => Some(current)
case head :: Nil => Some(head)
case xs =>
System.err.println("Search method is 'only' and multiple ancestor directories match:\n\t" + fromRoot.mkString("\n\t"))
System.exit(1)
None
}
case _ => Some(current)
}
val baseDirectory = found.getOrElse(current)
System.setProperty("user.dir", baseDirectory.getAbsolutePath)
(config.map(f => resolve(baseDirectory, f)), baseDirectory)
}
def resolve(baseDirectory: File, f: File): File =
{
assert(baseDirectory.isDirectory) // if base directory is not a directory, URI.resolve will not work properly
val uri = new URI(null, null, f.getPath, null)
new File(baseDirectory.toURI.resolve(uri))
}
private def hasProject(f: File) = f.isDirectory && search.paths.forall(p => resolve(f, p).exists)
private def path(f: File, acc: List[File]): List[File] = if(f eq null) acc else path(f.getParentFile, f :: acc)
}

View File

@ -6,27 +6,30 @@ import java.net.URL
object Launch object Launch
{ {
def apply(arguments: Seq[String]): Unit = apply( (new File("")).getAbsoluteFile , arguments ) def apply(arguments: Seq[String]): Unit = apply( (new File("")).getAbsoluteFile , arguments )
def apply(currentDirectory: File, arguments: Seq[String]): Unit = def apply(currentDirectory: File, arguments: Seq[String]): Unit =
Configuration.find(arguments, currentDirectory) match { case (configLocation, newArguments) => configured(currentDirectory, configLocation, newArguments) } 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: Seq[String]): Unit =
parsed(currentDirectory, Configuration.parse(configLocation, currentDirectory) , arguments) {
val config = Configuration.parse(configLocation, currentDirectory)
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: Seq[String]): Unit =
ResolveVersions(parsed) match { case (resolved, finish) => explicit(currentDirectory, resolved, arguments, finish) } ResolveVersions(parsed) match { case (resolved, finish) => explicit(currentDirectory, resolved, arguments, finish) }
def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: Seq[String], setupComplete: () => Unit): Unit = def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: Seq[String], setupComplete: () => Unit): Unit =
launch( run(new Launch(explicit.boot.directory, explicit.repositories)) ) ( launch( run(new Launch(explicit.boot.directory, explicit.repositories)) ) (
RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments, setupComplete) ) RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments, setupComplete) )
final def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult = def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult =
{ {
import config._ import config._
val scalaProvider: xsbti.ScalaProvider = launcher.getScala(scalaVersion) val scalaProvider: xsbti.ScalaProvider = launcher.getScala(scalaVersion)
val appProvider: xsbti.AppProvider = scalaProvider.app(app) val appProvider: xsbti.AppProvider = scalaProvider.app(app)
val appConfig: xsbti.AppConfiguration = new AppConfiguration(arguments.toArray, workingDirectory, appProvider) val appConfig: xsbti.AppConfiguration = new AppConfiguration(arguments.toArray, workingDirectory, appProvider)
val main = appProvider.newMain() val main = appProvider.newMain()
setupComplete() setupComplete()
main.run(appConfig) main.run(appConfig)
@ -55,7 +58,7 @@ class Launch(val bootDirectory: File, repositories: Seq[Repository]) extends xsb
def launcher: xsbti.Launcher = Launch.this def launcher: xsbti.Launcher = Launch.this
lazy val parentLoader = new BootFilteredLoader(getClass.getClassLoader) lazy val parentLoader = new BootFilteredLoader(getClass.getClassLoader)
lazy val configuration = new UpdateConfiguration(bootDirectory, version, repositories) lazy val configuration = new UpdateConfiguration(bootDirectory, version, repositories)
lazy val libDirectory = new File(configuration.bootDirectory, baseDirectoryName(version)) lazy val libDirectory = new File(configuration.bootDirectory, baseDirectoryName(version))
lazy val scalaHome = new File(libDirectory, ScalaDirectoryName) lazy val scalaHome = new File(libDirectory, ScalaDirectoryName)

View File

@ -1,6 +1,7 @@
package xsbt.boot package xsbt.boot
import java.io.File import java.io.File
import java.net.URL
final case class LaunchConfiguration(scalaVersion: Version, app: Application, repositories: Seq[Repository], boot: BootSetup, logging: Logging) extends NotNull final case class LaunchConfiguration(scalaVersion: Version, app: Application, repositories: Seq[Repository], boot: BootSetup, logging: Logging) extends NotNull
{ {
@ -9,6 +10,7 @@ final case class LaunchConfiguration(scalaVersion: Version, app: Application, re
def withApp(app: Application) = LaunchConfiguration(scalaVersion, app, repositories, boot, logging) def withApp(app: Application) = LaunchConfiguration(scalaVersion, app, repositories, boot, logging)
def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging) def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging)
def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(Version.Explicit(newScalaVersion), app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging) def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(Version.Explicit(newScalaVersion), app.withVersion(Version.Explicit(newAppVersion)), repositories, boot, logging)
def map(f: File => File) = LaunchConfiguration(scalaVersion, app, repositories, boot.map(f), logging)
} }
sealed trait Version extends NotNull sealed trait Version extends NotNull
@ -31,9 +33,10 @@ object Version
def default = Implicit(Implicit.ReadOrPrompt, None) def default = Implicit(Implicit.ReadOrPrompt, None)
} }
trait RichEnum extends Enumeration sealed abstract class RichEnum extends Enumeration
{ {
def fromString(s: String): Either[String, Value] = valueOf(s).toRight("Expected one of " + elements.mkString(",")) def fromString(s: String): Either[String, Value] = valueOf(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: Seq[String], crossVersioned: Boolean) extends NotNull
@ -42,7 +45,7 @@ final case class Application(groupID: String, name: String, version: Version, ma
def withVersion(newVersion: Version) = Application(groupID, name, newVersion, main, components, crossVersioned) 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, components.toArray, crossVersioned)
} }
case class AppID(groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersioned: Boolean) extends xsbti.ApplicationID final case class AppID(groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersioned: Boolean) extends xsbti.ApplicationID
object Application object Application
{ {
@ -56,8 +59,8 @@ object Application
sealed trait Repository extends NotNull sealed trait Repository extends NotNull
object Repository object Repository
{ {
final case class Maven(id: String, url: String) extends Repository final case class Maven(id: String, url: URL) extends Repository
final case class Ivy(id: String, url: String, pattern: String) extends Repository final case class Ivy(id: String, url: URL, pattern: String) extends Repository
final case class Predefined(id: Predefined.Value) extends Repository final case class Predefined(id: Predefined.Value) extends Repository
object Predefined extends RichEnum object Predefined extends RichEnum
@ -66,14 +69,28 @@ object Repository
val MavenLocal = Value("maven-local") val MavenLocal = Value("maven-local")
val MavenCentral = Value("maven-central") val MavenCentral = Value("maven-central")
val ScalaToolsReleases = Value("scala-tools-releases") val ScalaToolsReleases = Value("scala-tools-releases")
val ScalaToolsSnapshots = Value("scala-tools-snapshot") val ScalaToolsSnapshots = Value("scala-tools-snapshots")
def apply(s: String): Either[String, Predefined] = fromString(s).right.map(t => Predefined(t)) def apply(s: String): Predefined = Predefined(toValue(s))
} }
def defaults: Seq[Repository] = Predefined.elements.map(Predefined.apply).toList def defaults: Seq[Repository] = Predefined.elements.map(Predefined.apply).toList
} }
final case class BootSetup(directory: File, properties: File) extends NotNull final case class Search(tpe: Search.Value, paths: Seq[File]) extends NotNull
object Search extends RichEnum
{
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)
}
final case class BootSetup(directory: File, properties: File, search: Search) extends NotNull
{
def map(f: File => File) = BootSetup(f(directory), f(properties), search)
}
final case class Logging(level: LogLevel.Value) extends NotNull final case class Logging(level: LogLevel.Value) extends NotNull
object LogLevel extends RichEnum object LogLevel extends RichEnum
{ {
@ -81,11 +98,11 @@ object LogLevel extends RichEnum
val Info = Value("info") val Info = Value("info")
val Warn = Value("warn") val Warn = Value("warn")
val Error = Value("error") val Error = Value("error")
def apply(s: String): Either[String, Logging] = fromString(s).right.map(t => Logging(t)) def apply(s: String): Logging = Logging(toValue(s))
} }
class AppConfiguration(val arguments: Array[String], val baseDirectory: File, val provider: xsbti.AppProvider) extends xsbti.AppConfiguration 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). // 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 // This indicates overrides toString because the exception class name is not needed to understand
// the error message. // the error message.
class BootException(override val toString: String) extends RuntimeException final class BootException(override val toString: String) extends RuntimeException

View File

@ -42,6 +42,7 @@ final class Update(config: UpdateConfiguration)
addResolvers(settings) addResolvers(settings)
settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName)) settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
settings.setBaseDir(bootDirectory) settings.setBaseDir(bootDirectory)
settings.setVariable("scala", scalaVersion)
settings settings
} }
private lazy val ivy = Ivy.newInstance(settings) private lazy val ivy = Ivy.newInstance(settings)
@ -81,7 +82,7 @@ final class Update(config: UpdateConfiguration)
addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default") addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default")
case UpdateApp(app) => case UpdateApp(app) =>
val resolvedName = if(app.crossVersioned) app.name + "_" + scalaVersion else app.name val resolvedName = if(app.crossVersioned) app.name + "_" + scalaVersion else app.name
addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "runtime(default)") addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)")
} }
update(moduleID, target) update(moduleID, target)
} }
@ -155,13 +156,13 @@ final class Update(config: UpdateConfiguration)
import Predefined._ import Predefined._
repo match repo match
{ {
case Maven(id, url) => mavenResolver(id, url) case Maven(id, url) => mavenResolver(id, url.toString)
case Ivy(id, url, pattern) => urlResolver(id, url, pattern) case Ivy(id, url, pattern) => urlResolver(id, url.toString, pattern)
case Predefined(Local) => localResolver(settings.getDefaultIvyUserDir.getAbsolutePath) case Predefined(Local) => localResolver(settings.getDefaultIvyUserDir.getAbsolutePath)
case Predefined(MavenLocal) => mavenLocal case Predefined(MavenLocal) => mavenLocal
case Predefined(MavenCentral) => mavenMainResolver case Predefined(MavenCentral) => mavenMainResolver
case Predefined(ScalaToolsReleases) => mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases") case Predefined(ScalaToolsReleases) => mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases")
case Predefined(ScalaToolsSnapshots) => mavenResolver("Scala-Tools Maven2 Snapshots Repository", "http://scala-tools.org/repo-snapshots") case Predefined(ScalaToolsSnapshots) => scalaSnapshots(scalaVersion)
} }
} }
private def onDefaultRepositoryCacheManager(settings: IvySettings)(f: DefaultRepositoryCacheManager => Unit) private def onDefaultRepositoryCacheManager(settings: IvySettings)(f: DefaultRepositoryCacheManager => Unit)
@ -209,6 +210,24 @@ final class Update(config: UpdateConfiguration)
resolver.addArtifactPattern(localIvyRoot + "/" + LocalArtifactPattern) resolver.addArtifactPattern(localIvyRoot + "/" + LocalArtifactPattern)
resolver resolver
} }
private val SnapshotPattern = """(\d+).(\d+).(\d+)-(\d{8})\.(\d{6})-(\d+|\+)""".r.pattern
private def scalaSnapshots(scalaVersion: String) =
{
val m = SnapshotPattern.matcher(scalaVersion)
if(m.matches)
{
val base = Seq(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
resolver.setName("Scala Tools Snapshots")
resolver.setM2compatible(true)
resolver.addArtifactPattern(pattern)
resolver
}
else
mavenResolver("Scala-Tools Maven2 Snapshots Repository", "http://scala-tools.org/repo-snapshots")
}
/** Logs the given message to a file and to the console. */ /** Logs the given message to a file and to the console. */
private def log(msg: String) = private def log(msg: String) =
{ {