From 421093616331c6c864801e7a74b9299cf0988f49 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 4 Dec 2012 11:28:40 -0500 Subject: [PATCH] Adding app-repositories feature to Launcher. * Added appRepositories method to Launcher interface * Added 'bootOnly' configuration, such that repositories can be used only for boot and not applications. * Added tests to ensure 'bootOnly' and 'mavenCompatible' behave well * Updated documentation on launcher to reflect new options. --- launch/ConfigurationParser.scala | 37 ++++++--- launch/Launch.scala | 5 +- launch/LaunchConfiguration.scala | 16 ++-- .../src/main/java/xsbti/Launcher.java | 5 ++ .../test/scala/ConfigurationParserTest.scala | 76 +++++++++++++++++++ src/sphinx/Detailed-Topics/Launcher.rst | 4 +- 6 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 launch/src/test/scala/ConfigurationParserTest.scala diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index cdac66a9e..f0122e81d 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -58,13 +58,15 @@ class ConfigurationParser case line => readLine(in, ParseLine(line,index) ::: accum, index+1) } private def newReader(file: File) = new InputStreamReader(new FileInputStream(file), "UTF-8") - def readRepositoriesConfig(file: File): List[xsbti.Repository] = + def readRepositoriesConfig(file: File): List[Repository.Repository] = Using(newReader(file))(readRepositoriesConfig) - def readRepositoriesConfig(reader: Reader): List[xsbti.Repository] = + def readRepositoriesConfig(reader: Reader): List[Repository.Repository] = Using(new BufferedReader(reader))(readRepositoriesConfig) - private def readRepositoriesConfig(in: BufferedReader): List[xsbti.Repository] = + def readRepositoriesConfig(s: String): List[Repository.Repository] = + Using(new StringReader(s))(readRepositoriesConfig) + private def readRepositoriesConfig(in: BufferedReader): List[Repository.Repository] = processRepositoriesConfig(processLines(readLine(in, Nil, 0))) - def processRepositoriesConfig(sections: SectionMap): List[xsbti.Repository] = + def processRepositoriesConfig(sections: SectionMap): List[Repository.Repository] = processSection(sections, "repositories", getRepositories)._1 // section -> configuration instance processing def processSections(sections: SectionMap): LaunchConfiguration = @@ -176,21 +178,32 @@ class ConfigurationParser val app = new Application(org, name, rev, main, components, toBoolean(crossVersioned), classpathExtra) (app, classifiers) } - def getRepositories(m: LabelMap): List[xsbti.Repository] = + def getRepositories(m: LabelMap): List[Repository.Repository] = { import Repository.{Ivy, Maven, Predefined} + val BootOnly = "bootOnly" + val MvnComp = "mavenCompatible" m.toList.map { case (key, None) => Predefined(key) + case (key, Some(BootOnly)) => Predefined(key, true) case (key, Some(value)) => - val r = trim(substituteVariables(value).split(",",4)) + val r = trim(substituteVariables(value).split(",",5)) val url = try { new URL(r(0)) } catch { case e: MalformedURLException => error("Invalid URL specified for '" + key + "': " + e.getMessage) } + // TODO - Clean this up a lot. r.tail match { - case both :: "mavenCompatible" :: Nil => Ivy(key, url, both, both, mavenCompatible=true) - case ivy :: art :: "mavenCompatible" :: Nil => Ivy(key, url, ivy, art, mavenCompatible=true) - case ivy :: art :: Nil => Ivy(key, url, ivy, art, mavenCompatible=false) - case both :: Nil => Ivy(key, url, both, both, mavenCompatible=false) - case Nil => Maven(key, url) - case _ => error("Could not parse %s: %s".format(key, value)) + case both :: MvnComp :: BootOnly :: Nil => Ivy(key, url, both, both, mavenCompatible=true, bootOnly=true) + case both :: BootOnly :: MvnComp :: Nil => Ivy(key, url, both, both, mavenCompatible=true, bootOnly=true) + case both :: MvnComp :: Nil => Ivy(key, url, both, both, mavenCompatible=true, bootOnly=false) + case both :: BootOnly :: Nil => Ivy(key, url, both, both, mavenCompatible=false, bootOnly=true) + case ivy :: art :: MvnComp :: BootOnly :: Nil => Ivy(key, url, ivy, art, mavenCompatible=true, bootOnly=true) + case ivy :: art :: BootOnly :: MvnComp :: Nil => Ivy(key, url, ivy, art, mavenCompatible=true, bootOnly=true) + case ivy :: art :: MvnComp :: Nil => Ivy(key, url, ivy, art, mavenCompatible=true, bootOnly=false) + case ivy :: art :: BootOnly :: Nil => Ivy(key, url, ivy, art, mavenCompatible=false, bootOnly=true) + case ivy :: art :: Nil => Ivy(key, url, ivy, art, mavenCompatible=false, bootOnly=false) + case BootOnly :: Nil => Maven(key, url, bootOnly=true) + case both :: Nil => Ivy(key, url, both, both, mavenCompatible=false, bootOnly=false) + case Nil => Maven(key, url) + case _ => error("Could not parse %s: %s".format(key, value)) } } } diff --git a/launch/Launch.scala b/launch/Launch.scala index 8b1a44ec1..b345535d9 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -97,7 +97,8 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i def globalLock: xsbti.GlobalLock = Locks def ivyHome = orNull(ivyOptions.ivyHome) - def ivyRepositories = repositories.toArray + def ivyRepositories = (repositories: List[xsbti.Repository]).toArray + def appRepositories = ((repositories filterNot (_.bootOnly)): List[xsbti.Repository]).toArray def isOverrideRepositories: Boolean = ivyOptions.isOverrideRepositories def checksums = checksumsList.toArray[String] @@ -278,7 +279,7 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i } object Launcher { - def apply(bootDirectory: File, repositories: List[xsbti.Repository]): xsbti.Launcher = + def apply(bootDirectory: File, repositories: List[Repository.Repository]): xsbti.Launcher = apply(bootDirectory, IvyOptions(None, Classifiers(Nil, Nil), repositories, BootConfiguration.DefaultChecksums, false)) def apply(bootDirectory: File, ivyOptions: IvyOptions): xsbti.Launcher = apply(bootDirectory, ivyOptions, GetLocks.find) diff --git a/launch/LaunchConfiguration.scala b/launch/LaunchConfiguration.scala index c50ef86dd..7fa9b1ef5 100644 --- a/launch/LaunchConfiguration.scala +++ b/launch/LaunchConfiguration.scala @@ -25,7 +25,7 @@ final case class LaunchConfiguration(scalaVersion: Value[String], ivyConfigurati def map(f: File => File) = LaunchConfiguration(scalaVersion, ivyConfiguration.map(f), app.map(f), boot.map(f), logging, appProperties) } -final case class IvyOptions(ivyHome: Option[File], classifiers: Classifiers, repositories: List[xsbti.Repository], checksums: List[String], isOverrideRepositories: Boolean) +final case class IvyOptions(ivyHome: Option[File], classifiers: Classifiers, repositories: List[Repository.Repository], checksums: List[String], isOverrideRepositories: Boolean) { def map(f: File => File) = IvyOptions(ivyHome.map(f), classifiers, repositories, checksums, isOverrideRepositories) } @@ -70,15 +70,19 @@ object Application object Repository { - final case class Maven(id: String, url: URL) extends xsbti.MavenRepository - final case class Ivy(id: String, url: URL, ivyPattern: String, artifactPattern: String, mavenCompatible: Boolean) extends xsbti.IvyRepository - final case class Predefined(id: xsbti.Predefined) extends xsbti.PredefinedRepository + trait Repository extends xsbti.Repository { + def bootOnly: Boolean + } + final case class Maven(id: String, url: URL, bootOnly: Boolean = false) extends xsbti.MavenRepository with Repository + final case class Ivy(id: String, url: URL, ivyPattern: String, artifactPattern: String, mavenCompatible: Boolean, bootOnly: Boolean = false) extends xsbti.IvyRepository with Repository + final case class Predefined(id: xsbti.Predefined, bootOnly: Boolean = false) extends xsbti.PredefinedRepository with Repository object Predefined { - def apply(s: String): Predefined = Predefined(xsbti.Predefined.toValue(s)) + def apply(s: String): Predefined = new Predefined(xsbti.Predefined.toValue(s), false) + def apply(s: String, bootOnly: Boolean): Predefined = new Predefined(xsbti.Predefined.toValue(s), bootOnly) } def isMavenLocal(repo: xsbti.Repository) = repo match { case p: xsbti.PredefinedRepository => p.id == xsbti.Predefined.MavenLocal; case _ => false } - def defaults: List[xsbti.Repository] = xsbti.Predefined.values.map(Predefined.apply).toList + def defaults: List[xsbti.Repository] = xsbti.Predefined.values.map(x => Predefined(x, false)).toList } final case class Search(tpe: Search.Value, paths: List[File]) diff --git a/launch/interface/src/main/java/xsbti/Launcher.java b/launch/interface/src/main/java/xsbti/Launcher.java index 30f30e3ef..528495a70 100644 --- a/launch/interface/src/main/java/xsbti/Launcher.java +++ b/launch/interface/src/main/java/xsbti/Launcher.java @@ -38,6 +38,11 @@ public interface Launcher * are the same ones used to load the launcher. */ public xsbti.Repository[] ivyRepositories(); + /** These are the repositories configured by this launcher + * which should be used by the application when resolving + * further artifacts. + */ + public xsbti.Repository[] appRepositories(); /** The user has configured the launcher with the only repositories * it wants to use for this applciation. */ diff --git a/launch/src/test/scala/ConfigurationParserTest.scala b/launch/src/test/scala/ConfigurationParserTest.scala new file mode 100644 index 000000000..840db3d79 --- /dev/null +++ b/launch/src/test/scala/ConfigurationParserTest.scala @@ -0,0 +1,76 @@ +package xsbt.boot + +import java.io.{File,InputStream} +import java.net.URL +import java.util.Properties +import xsbti._ +import org.specs2._ +import mutable.Specification +import sbt.IO.{createDirectory, touch,withTemporaryDirectory} + +object ConfigurationParserTest extends Specification +{ + "Configuration Parser" should { + "Correctly parse bootOnly" in { + + repoFileContains("""|[repositories] + | local: bootOnly""".stripMargin, + Repository.Predefined("local", true)) + + repoFileContains("""|[repositories] + | local""".stripMargin, + Repository.Predefined("local", false)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org""".stripMargin, + Repository.Maven("id", new URL("http://repo1.maven.org"), false)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, bootOnly""".stripMargin, + Repository.Maven("id", new URL("http://repo1.maven.org"), true)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath]""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[orgPath]", false, false)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], mavenCompatible""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[orgPath]", true, false)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], mavenCompatible, bootOnly""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[orgPath]", true, true)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], bootOnly, mavenCompatible""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[orgPath]", true, true)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], bootOnly""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[orgPath]", false, true)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], [artPath]""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[artPath]", false, false)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], [artPath], bootOnly""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[artPath]", false, true)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], [artPath], bootOnly, mavenCompatible""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[artPath]", true, true)) + + repoFileContains("""|[repositories] + | id: http://repo1.maven.org, [orgPath], [artPath], mavenCompatible, bootOnly""".stripMargin, + Repository.Ivy("id", new URL("http://repo1.maven.org"), "[orgPath]", "[artPath]", true, true)) + + } + } + + def repoFileContains(file: String, repo: Repository.Repository) = + loadRepoFile(file) must contain(repo) + + def loadRepoFile(file: String) = + (new ConfigurationParser) readRepositoriesConfig file +} diff --git a/src/sphinx/Detailed-Topics/Launcher.rst b/src/sphinx/Detailed-Topics/Launcher.rst index c9eb73050..b29f2f512 100644 --- a/src/sphinx/Detailed-Topics/Launcher.rst +++ b/src/sphinx/Detailed-Topics/Launcher.rst @@ -106,7 +106,7 @@ by the following grammar. ``'nl'`` is a newline or end of file and resources: "resources" ":" `path` ("," `path`)* repository: ( `predefinedRepository` | `customRepository` ) `nl` predefinedRepository: "local" | "maven-local" | "maven-central" - customRepository: `label` ":" `url` [ ["," `ivyPattern`] "," `artifactPattern` [", mavenCompatible"]] + customRepository: `label` ":" `url` [ ["," `ivyPattern`] ["," `artifactPattern`] [", mavenCompatible"] [", bootOnly"]] property: `label` ":" `propertyDefinition` ("," `propertyDefinition`)* propertyDefinition: `mode` "=" (`set` | `prompt`) mode: "quick" | "new" | "fill" @@ -157,7 +157,7 @@ The default configuration file for sbt looks like: [repositories] local - typesafe-ivy-releases: http://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext] + typesafe-ivy-releases: http://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly maven-central sonatype-snapshots: https://oss.sonatype.org/content/repositories/snapshots