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.
This commit is contained in:
Josh Suereth 2012-12-04 11:28:40 -05:00 committed by Mark Harrah
parent e07dc32b7e
commit 4210936163
6 changed files with 121 additions and 22 deletions

View File

@ -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))
}
}
}

View File

@ -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)

View File

@ -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])

View File

@ -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.
*/

View File

@ -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
}

View File

@ -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