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)
}
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])
{
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()
try { Launch(args) }
catch

View File

@ -5,7 +5,7 @@ import java.net.{URI, URL}
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]) =
args match
{

View File

@ -5,10 +5,10 @@ import scala.util.parsing.input.{Reader, StreamReader}
import java.lang.Character.isWhitespace
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
def apply(s: String): LaunchConfiguration = processResult(configuration(new CharSequenceReader(s, 0)))
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)
(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) =
(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 =
{
val (dir, m1) = file(m, "directory", new File(workingDirectory, "project/boot"))
val (props, m2) = file(m1, "properties", new File("project/build.properties"))
check(m2, "label")
BootSetup(dir, props)
val (dir, m1) = file(m, "directory", toFile("project/boot"))
val (props, m2) = file(m1, "properties", toFile("project/build.properties"))
val (search, m3) = getSearch(m2, props)
check(m3, "label")
BootSetup(dir, props, search)
}
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 =
{
@ -83,11 +92,10 @@ class ConfigurationParser(workingDirectory: File) extends Parsers with NotNull
{
import Repository.{Ivy, Maven, Predefined}
m.toSeq.map {
case (key, None) => Predefined(key).fold(err => error(err), x => x)
case (key, None) => Predefined(key)
case (key, Some(value)) =>
val r = trim(value.split(",",2))
val url = r(0)
if(url.isEmpty) error("No URL specified for '" + key + "'")
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)
}
}

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

@ -11,7 +11,10 @@ object Launch
Configuration.find(arguments, currentDirectory) match { case (configLocation, newArguments) => configured(currentDirectory, configLocation, newArguments) }
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 =
ResolveVersions(parsed) match { case (resolved, finish) => explicit(currentDirectory, resolved, arguments, finish) }
@ -20,7 +23,7 @@ object Launch
launch( run(new Launch(explicit.boot.directory, explicit.repositories)) ) (
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._
val scalaProvider: xsbti.ScalaProvider = launcher.getScala(scalaVersion)

View File

@ -1,6 +1,7 @@
package xsbt.boot
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
{
@ -9,6 +10,7 @@ final case class LaunchConfiguration(scalaVersion: Version, app: Application, re
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 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
@ -31,9 +33,10 @@ object Version
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
@ -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 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
{
@ -56,8 +59,8 @@ object Application
sealed trait Repository extends NotNull
object Repository
{
final case class Maven(id: String, url: String) extends Repository
final case class Ivy(id: String, url: String, pattern: String) extends Repository
final case class Maven(id: String, url: URL) extends 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
@ -66,14 +69,28 @@ object Repository
val MavenLocal = Value("maven-local")
val MavenCentral = Value("maven-central")
val ScalaToolsReleases = Value("scala-tools-releases")
val ScalaToolsSnapshots = Value("scala-tools-snapshot")
def apply(s: String): Either[String, Predefined] = fromString(s).right.map(t => Predefined(t))
val ScalaToolsSnapshots = Value("scala-tools-snapshots")
def apply(s: String): Predefined = Predefined(toValue(s))
}
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
object LogLevel extends RichEnum
{
@ -81,11 +98,11 @@ object LogLevel extends RichEnum
val Info = Value("info")
val Warn = Value("warn")
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).
// This indicates overrides toString because the exception class name is not needed to understand
// 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)
settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
settings.setBaseDir(bootDirectory)
settings.setVariable("scala", scalaVersion)
settings
}
private lazy val ivy = Ivy.newInstance(settings)
@ -81,7 +82,7 @@ final class Update(config: UpdateConfiguration)
addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default")
case UpdateApp(app) =>
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)
}
@ -155,13 +156,13 @@ final class Update(config: UpdateConfiguration)
import Predefined._
repo match
{
case Maven(id, url) => mavenResolver(id, url)
case Ivy(id, url, pattern) => urlResolver(id, url, pattern)
case Maven(id, url) => mavenResolver(id, url.toString)
case Ivy(id, url, pattern) => urlResolver(id, url.toString, pattern)
case Predefined(Local) => localResolver(settings.getDefaultIvyUserDir.getAbsolutePath)
case Predefined(MavenLocal) => mavenLocal
case Predefined(MavenCentral) => mavenMainResolver
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)
@ -209,6 +210,24 @@ final class Update(config: UpdateConfiguration)
resolver.addArtifactPattern(localIvyRoot + "/" + LocalArtifactPattern)
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. */
private def log(msg: String) =
{