diff --git a/compile/src/test/scala/CompileTest.scala b/compile/src/test/scala/CompileTest.scala index c68f2f1293..b122910aa8 100644 --- a/compile/src/test/scala/CompileTest.scala +++ b/compile/src/test/scala/CompileTest.scala @@ -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])) diff --git a/launch/Create.scala b/launch/Create.scala index 2e890a61bc..488790631f 100644 --- a/launch/Create.scala +++ b/launch/Create.scala @@ -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 @@ -17,8 +20,8 @@ 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.") @@ -26,7 +29,7 @@ object Initialize } } } - 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 diff --git a/launch/FilteredLoader.scala b/launch/FilteredLoader.scala index 7f78381969..53ad52d66e 100644 --- a/launch/FilteredLoader.scala +++ b/launch/FilteredLoader.scala @@ -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) + } } \ No newline at end of file diff --git a/launch/Find.scala b/launch/Find.scala index 8190bd9dfa..39a7a369aa 100644 --- a/launch/Find.scala +++ b/launch/Find.scala @@ -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) } \ No newline at end of file diff --git a/launch/Launch.scala b/launch/Launch.scala index 84bc828341..220b19607a 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -10,8 +10,6 @@ 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 = @@ -19,32 +17,27 @@ object Launch 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 = @@ -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) { @@ -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) diff --git a/launch/Locks.scala b/launch/Locks.scala index f273ff63c3..21aedf9347 100644 --- a/launch/Locks.scala +++ b/launch/Locks.scala @@ -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 { diff --git a/launch/src/test/scala/ScalaProviderTest.scala b/launch/src/test/scala/ScalaProviderTest.scala index 82e06e1c8b..d16b1c8bd8 100644 --- a/launch/src/test/scala/ScalaProviderTest.scala +++ b/launch/src/test/scala/ScalaProviderTest.scala @@ -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") @@ -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) - } - } -} +} \ No newline at end of file diff --git a/launch/test-sample/Apps.scala b/launch/test-sample/Apps.scala new file mode 100644 index 0000000000..7fb99891cc --- /dev/null +++ b/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) + } +} \ No newline at end of file diff --git a/project/build/Helpers.scala b/project/build/Helpers.scala new file mode 100644 index 0000000000..7e1e99d4c2 --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 628976ff39..d1eaf65bb2 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -2,7 +2,7 @@ import sbt._ import java.io.File -class XSbt(info: ProjectInfo) extends ParentProject(info) +class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths { /* Subproject declarations*/ @@ -34,8 +34,6 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) val altCompilerSub = baseProject("main", "Alternate Compiler Test", stdTaskSub, logSub) - val distSub = project("dist", "Distribution", new DistProject(_)) - def baseProject(path: Path, name: String, deps: Project*) = project(path, name, new Base(_), deps : _*) /* Multi-subproject paths */ @@ -45,51 +43,38 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) def utilPath = path("util") def compilePath = path("compile") - class DistProject(info: ProjectInfo) extends Base(info) with ManagedBase - { - lazy val interDependencies = (XSbt.this.dependencies.toList -- List(distSub, launchSub, launchInterfaceSub, interfaceSub, compileInterfaceSub)) flatMap { - case b: Base => b :: Nil; case _ => Nil - } - override def dependencies = interfaceSub :: compileInterfaceSub :: interDependencies - lazy val dist = increment dependsOn(publishLocal) - override def artifactID = "xsbt" - } - - def increment = task { - val Array(keep, inc) = projectVersion.value.toString.split("_") - projectVersion() = Version.fromString(keep + "_" + (inc.toInt + 1)).right.get - log.info("Version is now " + projectVersion.value) - None - } - def compilerInterfaceClasspath = compileInterfaceSub.projectClasspath(Configurations.Test) //run in parallel override def parallelExecution = true - override def disableCrossPaths = true def jlineRev = "0.9.94" def jlineDep = "jline" % "jline" % jlineRev intransitive() + // publish locally when on repository server override def managedStyle = ManagedStyle.Ivy val publishTo = Resolver.file("test-repo", new File("/var/dbwww/repo/")) /* Subproject configurations*/ - class LaunchProject(info: ProjectInfo) extends Base(info) with TestWithIO with TestDependencies with ProguardLaunch + class LaunchProject(info: ProjectInfo) extends Base(info) with TestWithIO with TestDependencies with ProguardLaunch with NoCrossPaths { val jline = jlineDep val ivy = "org.apache.ivy" % "ivy" % "2.0.0" - def rawJarPath = jarPath - override def disableCrossPaths = true - lazy val rawPackage = packageTask(packagePaths +++ launchInterfaceSub.packagePaths, rawJarPath, packageOptions).dependsOn(compile) - // to test the retrieving and loading of the main sbt, we package and publish the test classes to the local repository - override def defaultMainArtifact = Artifact(testID) - override def projectID = ModuleID(organization, testID, "test-" + version) - override def packageAction = packageTask(packageTestPaths, outputPath / (testID + "-" + projectID.revision +".jar"), packageOptions).dependsOn(rawTestCompile) override def deliverProjectDependencies = Nil - def testID = "launch-test" + + // defines the package that proguard operates on + def rawJarPath = jarPath + def rawPackage = `package` + override def packagePaths = super.packagePaths +++ launchInterfaceSub.packagePaths + + // configure testing override def testClasspath = super.testClasspath +++ interfaceSub.compileClasspath +++ interfaceSub.mainResourcesPath - lazy val rawTestCompile = super.testCompileAction dependsOn(interfaceSub.compile) - override def testCompileAction = publishLocal dependsOn(rawTestCompile, interfaceSub.publishLocal) + override def testCompileAction = super.testCompileAction dependsOn(interfaceSub.publishLocal, testSamples.publishLocal) + + // used to test the retrieving and loading of an application: sample app is packaged and published to the local repository + lazy val testSamples = project("test-sample", "Launch Test", new TestSamples(_), interfaceSub, launchInterfaceSub) + class TestSamples(info: ProjectInfo) extends Base(info) with NoCrossPaths with NoPublish { + override def deliverProjectDependencies = Nil + } } trait TestDependencies extends Project { @@ -211,29 +196,4 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) override def testCompileAction = super.testCompileAction dependsOn((testWithTestClasspath.map(_.testCompile) ++ testWithCompileClasspath.map(_.compile)) : _*) override def testClasspath = (super.testClasspath /: (testWithTestClasspath.map(_.testClasspath) ++ testWithCompileClasspath.map(_.compileClasspath) ))(_ +++ _) } -} -trait JavaProject extends BasicScalaProject -{ - override def disableCrossPaths = true - // 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 -{ - override def disableCrossPaths = true - 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 } \ No newline at end of file