From 3b28156f84542955cd8a78cf6929421074e4d259 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 18 Dec 2009 17:46:57 -0500 Subject: [PATCH] Can specify in launcher configuration extra paths to go on the application classpath: [app] ... resources: conf, resources --- launch/ConfigurationParser.scala | 11 +++--- launch/Launch.scala | 4 ++- launch/LaunchConfiguration.scala | 13 +++---- launch/Provider.scala | 6 ++-- .../src/main/java/xsbti/ApplicationID.java | 5 +++ launch/src/test/scala/ScalaProviderTest.scala | 36 ++++++++++++++++--- project/build/XSbt.scala | 6 ++-- 7 files changed, 62 insertions(+), 19 deletions(-) diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index 55f8e4160..c521f6a1e 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -60,7 +60,8 @@ class ConfigurationParser extends NotNull val (b, m) = id(map, name, default.toString) (toBoolean(b), m) } - def toFile(path: String): File = new File(path.replace('/', File.separatorChar))// if the path is relative, it will be resolve by Launch later + def toFiles(paths: List[String]): List[File] = paths.map(toFile) + def toFile(path: String): File = new File(path.replace('/', File.separatorChar))// if the path is relative, it will be resolved by Launch later def file(map: LabelMap, name: String, default: File): (File, LabelMap) = (getOrNone(map, name).map(toFile).getOrElse(default), map - name) @@ -82,7 +83,7 @@ class ConfigurationParser extends NotNull { case (Nil, newM) => (Search.none, newM) case (tpe :: Nil, newM) => (Search(tpe, List(defaultPath)), newM) - case (tpe :: paths, newM) => (Search(tpe, paths.map(toFile)), newM) + case (tpe :: paths, newM) => (Search(tpe, toFiles(paths)), newM) } def getApplication(m: LabelMap): Application = @@ -93,8 +94,10 @@ class ConfigurationParser extends NotNull val (main, m4) = id(m3, "class", "xsbt.Main") 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, toBoolean(crossVersioned)) + val (resources, m7) = ids(m6, "resources", Nil) + check(m7, "label") + val classpathExtra = toFiles(resources).toArray[File] + new Application(org, name, rev, main, components, toBoolean(crossVersioned), classpathExtra) } def getRepositories(m: LabelMap): List[Repository] = { diff --git a/launch/Launch.scala b/launch/Launch.scala index cdedf715b..2b1c60141 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -96,6 +96,7 @@ class Launch(val bootDirectory: File, repositories: List[Repository]) extends xs def target = UpdateScala def failLabel = "Scala " + version def lockFile = updateLockFile + def extraClasspath = Array[File]() def app(id: xsbti.ApplicationID): xsbti.AppProvider = new AppProvider(id) @@ -110,7 +111,8 @@ class Launch(val bootDirectory: File, repositories: List[Repository]) extends xs def target = new UpdateApp(Application(id)) def failLabel = id.name + " " + id.version def lockFile = updateLockFile - def mainClasspath = classpath + def mainClasspath = classpath ++ extraClasspath + def extraClasspath = id.classpathExtra lazy val mainClass: Class[T] forSome { type T <: xsbti.AppMain } = { diff --git a/launch/LaunchConfiguration.scala b/launch/LaunchConfiguration.scala index 0b3236048..110d2cd01 100644 --- a/launch/LaunchConfiguration.scala +++ b/launch/LaunchConfiguration.scala @@ -11,7 +11,7 @@ final case class LaunchConfiguration(scalaVersion: Version, app: Application, re def withApp(app: Application) = LaunchConfiguration(scalaVersion, app, 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) + def map(f: File => File) = LaunchConfiguration(scalaVersion, app.map(f), repositories, boot.map(f), logging, appProperties) } sealed trait Version extends NotNull @@ -32,20 +32,21 @@ object Version def get(v: Version) = v match { case e: Version.Explicit => e.value; case _ => throw new BootException("Unresolved version: " + v) } } -final case class Application(groupID: String, name: String, version: Version, main: String, components: List[String], crossVersioned: Boolean) extends NotNull +final case class Application(groupID: String, name: String, version: Version, main: String, components: List[String], crossVersioned: Boolean, classpathExtra: Array[File]) 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, toArray(components), crossVersioned) + def withVersion(newVersion: Version) = Application(groupID, name, newVersion, main, components, crossVersioned, classpathExtra) + def toID = AppID(groupID, name, getVersion, main, toArray(components), crossVersioned, classpathExtra) + def map(f: File => File) = Application(groupID, name, version, main, components, crossVersioned, classpathExtra.map(f)) } -final 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, classpathExtra: Array[File]) extends xsbti.ApplicationID object Application { def apply(id: xsbti.ApplicationID): Application = { import id._ - Application(groupID, name, new Version.Explicit(version), mainClass, mainComponents.toList, crossVersioned) + Application(groupID, name, new Version.Explicit(version), mainClass, mainComponents.toList, crossVersioned, classpathExtra) } } diff --git a/launch/Provider.scala b/launch/Provider.scala index c0ada0394..fd3edc850 100644 --- a/launch/Provider.scala +++ b/launch/Provider.scala @@ -10,6 +10,7 @@ trait Provider extends NotNull def configuration: UpdateConfiguration def baseDirectories: List[File] def testLoadClasses: List[String] + def extraClasspath: Array[File] def target: UpdateTarget def failLabel: String def parentLoader: ClassLoader @@ -46,8 +47,8 @@ trait Provider extends NotNull } def createLoader = { - val jars = classpath - (jars, new URLClassLoader(jars.map(_.toURI.toURL), parentLoader) ) + val fullClasspath = classpath ++ extraClasspath + (fullClasspath, new URLClassLoader(Provider.toURLs(fullClasspath), parentLoader) ) } } } @@ -66,4 +67,5 @@ object Provider def classMissing(c: String) = try { Class.forName(c, false, loader); false } catch { case e: ClassNotFoundException => true } classes.toList.filter(classMissing) } + def toURLs(files: Array[File]): Array[URL] = files.map(_.toURI.toURL) } \ No newline at end of file diff --git a/launch/interface/src/main/java/xsbti/ApplicationID.java b/launch/interface/src/main/java/xsbti/ApplicationID.java index fd4757348..17331d82a 100644 --- a/launch/interface/src/main/java/xsbti/ApplicationID.java +++ b/launch/interface/src/main/java/xsbti/ApplicationID.java @@ -1,5 +1,7 @@ package xsbti; +import java.io.File; + public interface ApplicationID { public String groupID(); @@ -9,4 +11,7 @@ public interface ApplicationID public String mainClass(); public String[] mainComponents(); public boolean crossVersioned(); + + /** Files to add to the application classpath. */ + public File[] classpathExtra(); } \ No newline at end of file diff --git a/launch/src/test/scala/ScalaProviderTest.scala b/launch/src/test/scala/ScalaProviderTest.scala index 55a2381eb..b5fb67dae 100644 --- a/launch/src/test/scala/ScalaProviderTest.scala +++ b/launch/src/test/scala/ScalaProviderTest.scala @@ -16,6 +16,8 @@ object ScalaProviderTest extends Specification "ClassLoader for Scala 2.7.3" in { checkScalaLoader("2.7.3") } "ClassLoader for Scala 2.7.4" in { checkScalaLoader("2.7.4") } "ClassLoader for Scala 2.7.5" in { checkScalaLoader("2.7.5") } + "ClassLoader for Scala 2.7.6" in { checkScalaLoader("2.7.6") } + "ClassLoader for Scala 2.7.7" in { checkScalaLoader("2.7.7") } } "Launch" should { @@ -26,16 +28,29 @@ object ScalaProviderTest extends Specification "Successfully load an application from local repository and run it with correct sbt version" in { checkLoad(List(AppVersion), "xsbt.boot.test.AppVersionTest").asInstanceOf[Exit].code must be(0) } + "Add extra resources to the classpath" in { + checkLoad(testResources, "xsbt.boot.test.ExtraTest", createExtra).asInstanceOf[Exit].code must be(0) + } } - private def checkLoad(arguments: List[String], mainClassName: String): MainResult = + def checkLoad(arguments: List[String], mainClassName: String): MainResult = + checkLoad(arguments, mainClassName, _ => Array[File]()) + def checkLoad(arguments: List[String], mainClassName: String, extra: File => Array[File]): MainResult = FileUtilities.withTemporaryDirectory { currentDirectory => withLauncher { launcher => Launch.run(launcher)( - new RunConfiguration(mapScalaVersion(LaunchTest.getScalaVersion), LaunchTest.testApp(mainClassName).toID, currentDirectory, arguments) + new RunConfiguration(mapScalaVersion(LaunchTest.getScalaVersion), LaunchTest.testApp(mainClassName, extra(currentDirectory)).toID, currentDirectory, arguments) ) } } + private def testResources = List("test-resourceA", "a/b/test-resourceB", "sub/test-resource") + private def createExtra(currentDirectory: File) = + { + val resourceDirectory = new File(currentDirectory, "resources") + FileUtilities.createDirectory(resourceDirectory) + testResources.foreach(resource => FileUtilities.touch(new File(resourceDirectory, resource.replace('/', File.separatorChar)))) + Array(resourceDirectory) + } private def checkScalaLoader(version: String): Unit = withLauncher( checkLauncher(version, scalaVersionMap(version)) ) private def checkLauncher(version: String, versionValue: String)(launcher: Launcher): Unit = { @@ -49,7 +64,8 @@ object ScalaProviderTest extends Specification } object LaunchTest { - def testApp(main: String) = Application("org.scala-tools.sbt", "launch-test", new Version.Explicit(AppVersion), main, Nil, false) + def testApp(main: String): Application = testApp(main, Array[File]()) + def testApp(main: String, extra: Array[File]): Application = Application("org.scala-tools.sbt", "launch-test", new Version.Explicit(AppVersion), main, Nil, false, extra) import Repository.Predefined._ def testRepositories = List(Local, ScalaToolsReleases, ScalaToolsSnapshots).map(Repository.Predefined.apply) def withLauncher[T](f: xsbti.Launcher => T): T = @@ -59,7 +75,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") ) ++ List("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", "2.7.6", "2.7.7").map(v => (v, v + ".final")) def getScalaVersion: String = getScalaVersion(getClass.getClassLoader) def getScalaVersion(loader: ClassLoader): String = { @@ -71,6 +87,7 @@ object LaunchTest lazy val AppVersion = { val properties = new java.util.Properties + println(getClass.getResource("/xsbt.version.properties")) val propertiesStream = getClass.getResourceAsStream("/xsbt.version.properties") try { properties.load(propertiesStream) } finally { propertiesStream.close() } "test-" + properties.getProperty("version") @@ -99,4 +116,15 @@ package test 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) + } + } } diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 3501f7aa7..76fd2a66a 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -81,7 +81,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) override def packageAction = packageTask(packageTestPaths, outputPath / (testID + "-" + projectID.revision +".jar"), packageOptions).dependsOn(rawTestCompile) override def deliverProjectDependencies = Nil def testID = "launch-test" - override def testClasspath = super.testClasspath +++ interfaceSub.compileClasspath + 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) } @@ -131,7 +131,9 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) val formatter = new java.text.SimpleDateFormat("yyyyMMdd'T'HHmmss") formatter.setTimeZone(TimeZone.getTimeZone("GMT")) val timestamp = formatter.format(new Date) - FileUtilities.write(versionPropertiesPath.asFile, "version=" + version + "\ntimestamp=" + timestamp, log) + val content = "version=" + version + "\ntimestamp=" + timestamp + log.info("Writing version information to " + versionPropertiesPath + " :\n" + content) + FileUtilities.write(versionPropertiesPath.asFile, content, log) } override def watchPaths = super.watchPaths +++ apiDefinitionPaths --- sources(generatedBasePath)