Skip to content

Commit

Permalink
* cleaned up build
Browse files Browse the repository at this point in the history
 * made Launcher usable outside of official jar
  • Loading branch information
harrah committed Mar 19, 2010
1 parent e1e60fe commit 54bc694
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 130 deletions.
2 changes: 1 addition & 1 deletion compile/src/test/scala/CompileTest.scala
Expand Up @@ -41,7 +41,7 @@ object WithCompiler
boot.LaunchTest.withLauncher { launch =>
FileUtilities.withTemporaryDirectory { componentDirectory =>
val manager = new ComponentManager(xsbt.boot.Locks, new boot.ComponentProvider(componentDirectory), log)
val compiler = new AnalyzingCompiler(ScalaInstance(scalaVersion, launch), manager)
val compiler = new AnalyzingCompiler(ScalaInstance(scalaVersion, launch), manager, log)
compiler.newComponentCompiler(log).clearCache(ComponentCompiler.compilerInterfaceID)
define(manager, ComponentCompiler.compilerInterfaceSrcID, getResource("CompilerInterface.scala"), getClassResource(classOf[jline.Completor]))
define(manager, ComponentCompiler.xsbtiID, getClassResource(classOf[xsbti.AnalysisCallback]))
Expand Down
9 changes: 6 additions & 3 deletions launch/Create.scala
Expand Up @@ -9,6 +9,9 @@ import java.util.Properties

object Initialize
{
lazy val selectCreate = (_: AppProperty).create
lazy val selectQuick = (_: AppProperty).quick
lazy val selectFill = (_: AppProperty).fill
def create(file: File, promptCreate: String, enableQuick: Boolean, spec: List[AppProperty])
{
SimpleReader.readLine(promptCreate + " (y/N" + (if(enableQuick) "/s" else "") + ") ") match
Expand All @@ -17,16 +20,16 @@ object Initialize
case Some(line) =>
line.toLowerCase match
{
case "y" | "yes" => process(file, spec, _.create)
case "s" => process(file, spec, _.quick)
case "y" | "yes" => process(file, spec, selectCreate)
case "s" => process(file, spec, selectQuick)
case "n" | "no" | "" => declined("")
case x =>
System.out.println(" '" + x + "' not understood.")
create(file, promptCreate, enableQuick, spec)
}
}
}
def fill(file: File, spec: List[AppProperty]): Unit = process(file, spec, _.fill)
def fill(file: File, spec: List[AppProperty]): Unit = process(file, spec, selectFill)
def process(file: File, appProperties: List[AppProperty], select: AppProperty => Option[PropertyInit])
{
val properties = new Properties
Expand Down
18 changes: 16 additions & 2 deletions launch/FilteredLoader.scala
Expand Up @@ -18,7 +18,21 @@ private[boot] final class BootFilteredLoader(parent: ClassLoader) extends ClassL
else
super.loadClass(className, resolve)
}
override def getResources(name: String) = if(includeResource(name)) super.getResources(name) else parent.getParent.getResources(name)
override def getResource(name: String) = if(includeResource(name)) super.getResource(name) else parent.getParent.getResource(name)
override def getResources(name: String) = if(includeResource(name)) super.getResources(name) else excludedLoader.getResources(name)
override def getResource(name: String) = if(includeResource(name)) super.getResource(name) else excludedLoader.getResource(name)
def includeResource(name: String) = name.startsWith(JLinePackagePath)
// the loader to use when a resource is excluded. This needs to be at least parent.getParent so that it skips parent. parent contains
// resources included in the launcher, which need to be ignored. Now that launcher can be unrooted (not the application entry point),
// this needs to be the Java extension loader (the loader with getParent == null)
private val excludedLoader = Loaders(parent.getParent).head
}

object Loaders
{
def apply(loader: ClassLoader): Stream[ClassLoader] =
{
def loaders(loader: ClassLoader, accum: Stream[ClassLoader]): Stream[ClassLoader] =
if(loader eq null) accum else loaders(loader.getParent, Stream.cons(loader, accum))
loaders(getClass.getClassLoader.getParent, Stream.empty)
}
}
13 changes: 9 additions & 4 deletions launch/Find.scala
Expand Up @@ -39,14 +39,19 @@ class Find(config: LaunchConfiguration) extends NotNull
}
val baseDirectory = found.getOrElse(current)
System.setProperty("user.dir", baseDirectory.getAbsolutePath)
(config.map(f => resolve(baseDirectory, f)), baseDirectory)
(ResolvePaths(config, baseDirectory), baseDirectory)
}
def resolve(baseDirectory: File, f: File): File =
private def hasProject(f: File) = f.isDirectory && search.paths.forall(p => ResolvePaths(f, p).exists)
private def path(f: File, acc: List[File]): List[File] = if(f eq null) acc else path(f.getParentFile, f :: acc)
}
object ResolvePaths
{
def apply(config: LaunchConfiguration, baseDirectory: File): LaunchConfiguration =
config.map(f => apply(baseDirectory, f))
def apply(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)
}
36 changes: 23 additions & 13 deletions launch/Launch.scala
Expand Up @@ -10,41 +10,34 @@ import java.net.URL

object Launch
{
//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: List[String]): Unit =
Configuration.find(arguments, currentDirectory) match { case (configLocation, newArguments) => configured(currentDirectory, configLocation, newArguments) }

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: List[String]): Unit =
{
time("found working directory")
val propertiesFile = parsed.boot.properties
import parsed.boot.{enableQuick, promptCreate, promptFill}
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: List[String]): Unit =
{
val resolved = ResolveVersions(parsed)
time("resolved")
explicit(currentDirectory, resolved, arguments)
}

def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: List[String]): Unit =
launch( run(new Launch(explicit.boot.directory, explicit.repositories, explicit.scalaClassifiers)) ) (
launch( run(Launcher(explicit)) ) (
new RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments) )

def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult =
Expand All @@ -54,12 +47,8 @@ object Launch
val appProvider: xsbti.AppProvider = scalaProvider.app(app)
val appConfig: xsbti.AppConfiguration = new AppConfiguration(toArray(arguments), workingDirectory, appProvider)

time("pre-load")
val main = appProvider.newMain()
time("loaded")
val result = main.run(appConfig)
time("ran")
result
main.run(appConfig)
}
final def launch(run: RunConfiguration => xsbti.MainResult)(config: RunConfiguration)
{
Expand Down Expand Up @@ -130,6 +119,27 @@ class Launch(val bootDirectory: File, repositories: List[Repository], scalaClass
}
}
}
object Launcher
{
def apply(bootDirectory: File, repositories: List[Repository], scalaClassifiers: List[String]): xsbti.Launcher =
apply(bootDirectory, repositories, scalaClassifiers, GetLocks.find)
def apply(bootDirectory: File, repositories: List[Repository], scalaClassifiers: List[String], locks: xsbti.GlobalLock): xsbti.Launcher =
new Launch(bootDirectory, repositories, scalaClassifiers) {
override def globalLock = locks
}
def apply(explicit: LaunchConfiguration): xsbti.Launcher =
new Launch(explicit.boot.directory, explicit.repositories, explicit.scalaClassifiers)
def defaultAppProvider(baseDirectory: File): xsbti.AppProvider = getAppProvider(baseDirectory, Configuration.configurationOnClasspath)
def getAppProvider(baseDirectory: File, configLocation: URL): xsbti.AppProvider =
{
val parsed = ResolvePaths(Configuration.parse(configLocation, baseDirectory), baseDirectory)
Initialize.process(parsed.boot.properties, parsed.appProperties, Initialize.selectQuick)
val config = ResolveVersions(parsed)
val launcher = apply(config)
val scalaProvider = launcher.getScala(config.getScalaVersion)
scalaProvider.app(config.app.toID)
}
}
class ComponentProvider(baseDirectory: File) extends xsbti.ComponentProvider
{
def componentLocation(id: String): File = new File(baseDirectory, id)
Expand Down
12 changes: 12 additions & 0 deletions launch/Locks.scala
Expand Up @@ -7,6 +7,18 @@ import java.io.{File, FileOutputStream}
import java.nio.channels.FileChannel
import java.util.concurrent.Callable

object GetLocks
{
/** Searches for Locks in parent class loaders before returning Locks from this class loader.
* Normal class loading doesn't work because the launcher class loader hides xsbt classes.*/
def find: xsbti.GlobalLock =
Loaders(getClass.getClassLoader.getParent).flatMap(tryGet).headOption.getOrElse(Locks)
private[this] def tryGet(loader: ClassLoader): List[xsbti.GlobalLock] =
try { getLocks0(loader) :: Nil } catch { case e: ClassNotFoundException => Nil }
private[this] def getLocks0(loader: ClassLoader) =
Class.forName("xsbt.boot.Locks$", true, loader).getField("MODULE$").get(null).asInstanceOf[xsbti.GlobalLock]
}

// gets a file lock by first getting a JVM-wide lock.
object Locks extends xsbti.GlobalLock
{
Expand Down
58 changes: 8 additions & 50 deletions launch/src/test/scala/ScalaProviderTest.scala
Expand Up @@ -6,8 +6,6 @@ import xsbti._
import org.specs._
import LaunchTest._

final class Main // needed so that when we test Launch, it doesn't think sbt was improperly downloaded (it looks for xsbt.Main to verify the right jar was downloaded)

object ScalaProviderTest extends Specification
{
def provide = addToSusVerb("provide")
Expand Down Expand Up @@ -70,61 +68,21 @@ object LaunchTest
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, Nil))
f(Launcher(bootDirectory, testRepositories, Nil))
}

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") ) ++ List("2.7.3", "2.7.4", "2.7.5", "2.7.6", "2.7.7").map(v => (v, v + ".final"))
def getScalaVersion: String = getScalaVersion(getClass.getClassLoader)
def getScalaVersion(loader: ClassLoader): String =
{
val propertiesStream = loader.getResourceAsStream("library.properties")
val properties = new Properties
properties.load(propertiesStream)
properties.getProperty("version.number")
}
lazy val AppVersion =
def getScalaVersion(loader: ClassLoader): String = loadProperties(loader, "library.properties").getProperty("version.number")
lazy val AppVersion = loadProperties(getClass.getClassLoader, "xsbt.version.properties").getProperty("version")
private def getProperty(loader: ClassLoader, res: String, key: String) = loadProperties(loader, res).getProperty(key)
private def loadProperties(loader: ClassLoader, res: String): Properties =
{
val properties = new java.util.Properties
println(getClass.getResource("/xsbt.version.properties"))
val propertiesStream = getClass.getResourceAsStream("/xsbt.version.properties")
val propertiesStream = loader.getResourceAsStream(res)
try { properties.load(propertiesStream) } finally { propertiesStream.close() }
"test-" + properties.getProperty("version")
}
}
package test
{
class Exit(val code: Int) extends xsbti.Exit
final class MainException(message: String) extends RuntimeException(message)
final class ArgumentTest extends AppMain
{
def run(configuration: xsbti.AppConfiguration) =
if(configuration.arguments.length == 0)
throw new MainException("Arguments were empty")
else
new Exit(0)
properties
}
class AppVersionTest extends AppMain
{
def run(configuration: xsbti.AppConfiguration) =
{
val expected = configuration.arguments.headOption.getOrElse("")
if(configuration.provider.id.version == expected)
new Exit(0)
else
throw new MainException("app version was " + configuration.provider.id.version + ", expected: " + expected)
}
}
class ExtraTest extends AppMain
{
def run(configuration: xsbti.AppConfiguration) =
{
configuration.arguments.foreach { arg =>
if(getClass.getClassLoader.getResource(arg) eq null)
throw new MainException("Could not find '" + arg + "'")
}
new Exit(0)
}
}
}
}
35 changes: 35 additions & 0 deletions launch/test-sample/Apps.scala
@@ -0,0 +1,35 @@
/** These are packaged and published locally and the resulting artifact is used to test the launcher.*/
package xsbt.boot.test

class Exit(val code: Int) extends xsbti.Exit
final class MainException(message: String) extends RuntimeException(message)
final class ArgumentTest extends xsbti.AppMain
{
def run(configuration: xsbti.AppConfiguration) =
if(configuration.arguments.length == 0)
throw new MainException("Arguments were empty")
else
new Exit(0)
}
class AppVersionTest extends xsbti.AppMain
{
def run(configuration: xsbti.AppConfiguration) =
{
val expected = configuration.arguments.headOption.getOrElse("")
if(configuration.provider.id.version == expected)
new Exit(0)
else
throw new MainException("app version was " + configuration.provider.id.version + ", expected: " + expected)
}
}
class ExtraTest extends xsbti.AppMain
{
def run(configuration: xsbti.AppConfiguration) =
{
configuration.arguments.foreach { arg =>
if(getClass.getClassLoader.getResource(arg) eq null)
throw new MainException("Could not find '" + arg + "'")
}
new Exit(0)
}
}
34 changes: 34 additions & 0 deletions project/build/Helpers.scala
@@ -0,0 +1,34 @@
import sbt._

trait NoPublish extends ManagedBase
{
override final def publishAction = task { None }
override final def deliverAction = publishAction
}
trait NoCrossPaths extends Project
{
override def disableCrossPaths = true
}
trait JavaProject extends BasicScalaProject with NoCrossPaths
{
// ensure that interfaces are only Java sources and that they cannot reference Scala classes
override def mainSources = descendents(mainSourceRoots, "*.java")
override def compileOrder = CompileOrder.JavaThenScala
}
trait SourceProject extends BasicScalaProject with NoCrossPaths
{
override def packagePaths = mainResources +++ mainSources // the default artifact is a jar of the main sources and resources
}
trait ManagedBase extends BasicScalaProject
{
override def deliverScalaDependencies = Nil
override def managedStyle = ManagedStyle.Ivy
override def useDefaultConfigurations = false
val defaultConf = Configurations.Default
val testConf = Configurations.Test
}
trait Component extends DefaultProject
{
override def projectID = componentID match { case Some(id) => super.projectID extra("e:component" -> id); case None => super.projectID }
def componentID: Option[String] = None
}

0 comments on commit 54bc694

Please sign in to comment.