mirror of https://github.com/sbt/sbt.git
Add Ivy repository support
This commit is contained in:
parent
e4dfc862b4
commit
3b4b773c64
|
|
@ -30,6 +30,8 @@ case class CommonOptions(
|
|||
@HelpMessage("Repositories - for multiple repositories, separate with comma and/or repeat this option (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
|
||||
@ExtraName("r")
|
||||
repository: List[String],
|
||||
@HelpMessage("Do not add default repositories (~/.ivy2/local, and Central)")
|
||||
noDefault: Boolean = false,
|
||||
@HelpMessage("Force module version")
|
||||
@ValueDescription("organization:name:forcedVersion")
|
||||
@ExtraName("V")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ package cli
|
|||
import java.io.{ OutputStreamWriter, File }
|
||||
import java.util.UUID
|
||||
|
||||
import coursier.ivy.IvyRepository
|
||||
|
||||
import scalaz.{ \/-, -\/ }
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
|
|
@ -62,69 +64,66 @@ class Helper(
|
|||
else
|
||||
CachePolicy.Default
|
||||
|
||||
val cache = Cache(new File(cacheOptions.cache))
|
||||
cache.init(verbose = verbose0 >= 0)
|
||||
val files =
|
||||
Files(
|
||||
Seq(
|
||||
"http://" -> new File(new File(cacheOptions.cache), "http"),
|
||||
"https://" -> new File(new File(cacheOptions.cache), "https")
|
||||
),
|
||||
() => ???,
|
||||
concurrentDownloadCount = parallel
|
||||
)
|
||||
|
||||
val repositoryIds = {
|
||||
val repositoryIds0 = repository
|
||||
.flatMap(_.split(','))
|
||||
.map(_.trim)
|
||||
.filter(_.nonEmpty)
|
||||
val central = MavenRepository("https://repo1.maven.org/maven2/")
|
||||
val ivy2Local = MavenRepository(
|
||||
new File(sys.props("user.home") + "/.ivy2/local/").toURI.toString,
|
||||
ivyLike = true
|
||||
)
|
||||
val defaultRepositories = Seq(
|
||||
ivy2Local,
|
||||
central
|
||||
)
|
||||
|
||||
if (repositoryIds0.isEmpty)
|
||||
cache.default()
|
||||
else
|
||||
repositoryIds0
|
||||
}
|
||||
val repositories0 = common.repository.map { repo =>
|
||||
val repo0 = repo.toLowerCase
|
||||
if (repo0 == "central")
|
||||
Right(central)
|
||||
else if (repo0 == "ivy2local")
|
||||
Right(ivy2Local)
|
||||
else if (repo0.startsWith("sonatype:"))
|
||||
Right(
|
||||
MavenRepository(s"https://oss.sonatype.org/content/repositories/${repo.drop("sonatype:".length)}")
|
||||
)
|
||||
else {
|
||||
val (url, r) =
|
||||
if (repo.startsWith("ivy:")) {
|
||||
val url = repo.drop("ivy:".length)
|
||||
(url, IvyRepository(url))
|
||||
} else if (repo.startsWith("ivy-like:")) {
|
||||
val url = repo.drop("ivy-like:".length)
|
||||
(url, MavenRepository(url, ivyLike = true))
|
||||
} else {
|
||||
(repo, MavenRepository(repo))
|
||||
}
|
||||
|
||||
val repoMap = cache.map()
|
||||
val repoByBase = repoMap.map { case (_, v @ (m, _)) =>
|
||||
m.root -> v
|
||||
}
|
||||
|
||||
val repositoryIdsOpt0 = repositoryIds.map { id =>
|
||||
repoMap.get(id) match {
|
||||
case Some(v) => Right(v)
|
||||
case None =>
|
||||
if (id.contains("://")) {
|
||||
val root0 = if (id.endsWith("/")) id else id + "/"
|
||||
Right(
|
||||
repoByBase.getOrElse(root0, {
|
||||
val id0 = UUID.randomUUID().toString
|
||||
if (verbose0 >= 1)
|
||||
Console.err.println(s"Addding repository $id0 ($root0)")
|
||||
|
||||
// FIXME This could be done more cleanly
|
||||
cache.add(id0, root0, ivyLike = false)
|
||||
cache.map().getOrElse(id0,
|
||||
sys.error(s"Adding repository $id0 ($root0)")
|
||||
)
|
||||
})
|
||||
)
|
||||
} else
|
||||
Left(id)
|
||||
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file:/"))
|
||||
Right(r)
|
||||
else
|
||||
Left(repo -> s"Unrecognized protocol or repository: $url")
|
||||
}
|
||||
}
|
||||
|
||||
val notFoundRepositoryIds = repositoryIdsOpt0.collect {
|
||||
case Left(id) => id
|
||||
}
|
||||
|
||||
if (notFoundRepositoryIds.nonEmpty) {
|
||||
errPrintln(
|
||||
(if (notFoundRepositoryIds.lengthCompare(1) == 0) "Repository" else "Repositories") +
|
||||
" not found: " +
|
||||
notFoundRepositoryIds.mkString(", ")
|
||||
)
|
||||
|
||||
val unrecognizedRepos = repositories0.collect { case Left(e) => e }
|
||||
if (unrecognizedRepos.nonEmpty) {
|
||||
errPrintln(s"${unrecognizedRepos.length} error(s) parsing repositories:")
|
||||
for ((repo, err) <- unrecognizedRepos)
|
||||
errPrintln(s"$repo: $err")
|
||||
sys.exit(255)
|
||||
}
|
||||
|
||||
val files = cache.files().copy(concurrentDownloadCount = parallel)
|
||||
|
||||
val (repositories, fileCaches) = repositoryIdsOpt0
|
||||
.collect { case Right(v) => v }
|
||||
.unzip
|
||||
val repositories =
|
||||
(if (common.noDefault) Nil else defaultRepositories) ++
|
||||
repositories0.collect { case Right(r) => r }
|
||||
|
||||
val (rawDependencies, extraArgs) = {
|
||||
val idxOpt = Some(remainingArgs.indexOf("--")).filter(_ >= 0)
|
||||
|
|
|
|||
|
|
@ -46,11 +46,13 @@ package object compatibility {
|
|||
def label =
|
||||
option[String](node0.nodeName)
|
||||
.getOrElse("")
|
||||
def child =
|
||||
def children =
|
||||
option[NodeList](node0.childNodes)
|
||||
.map(l => List.tabulate(l.length)(l.item).map(fromNode))
|
||||
.getOrElse(Nil)
|
||||
|
||||
def attributes: Seq[(String, String)] = ???
|
||||
|
||||
// `exists` instead of `contains`, for scala 2.10
|
||||
def isText =
|
||||
option[Int](node0.nodeType)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package coursier.core
|
|||
|
||||
import coursier.util.Xml
|
||||
|
||||
import scala.xml.{ MetaData, Null }
|
||||
|
||||
package object compatibility {
|
||||
|
||||
implicit class RichChar(val c: Char) extends AnyVal {
|
||||
|
|
@ -16,8 +18,21 @@ package object compatibility {
|
|||
|
||||
def fromNode(node: scala.xml.Node): Xml.Node =
|
||||
new Xml.Node {
|
||||
lazy val attributes = {
|
||||
def helper(m: MetaData): Stream[(String, String)] =
|
||||
m match {
|
||||
case Null => Stream.empty
|
||||
case attr =>
|
||||
val value = attr.value.collect {
|
||||
case scala.xml.Text(t) => t
|
||||
}.mkString("")
|
||||
(attr.key -> value) #:: helper(m.next)
|
||||
}
|
||||
|
||||
helper(node.attributes).toVector
|
||||
}
|
||||
def label = node.label
|
||||
def child = node.child.map(fromNode)
|
||||
def children = node.child.map(fromNode)
|
||||
def isText = node match { case _: scala.xml.Text => true; case _ => false }
|
||||
def textContent = node.text
|
||||
def isElement = node match { case _: scala.xml.Elem => true; case _ => false }
|
||||
|
|
|
|||
|
|
@ -34,13 +34,16 @@ case class Dependency(
|
|||
module: Module,
|
||||
version: String,
|
||||
configuration: String,
|
||||
attributes: Attributes,
|
||||
exclusions: Set[(String, String)],
|
||||
|
||||
// Maven-specific
|
||||
attributes: Attributes,
|
||||
optional: Boolean
|
||||
) {
|
||||
def moduleVersion = (module, version)
|
||||
}
|
||||
|
||||
// Maven-specific
|
||||
case class Attributes(
|
||||
`type`: String,
|
||||
classifier: String
|
||||
|
|
@ -49,20 +52,30 @@ case class Attributes(
|
|||
case class Project(
|
||||
module: Module,
|
||||
version: String,
|
||||
// First String is configuration (scope for Maven)
|
||||
dependencies: Seq[(String, Dependency)],
|
||||
// For Maven, this is the standard scopes as an Ivy configuration
|
||||
configurations: Map[String, Seq[String]],
|
||||
|
||||
// Maven-specific
|
||||
parent: Option[(Module, String)],
|
||||
dependencyManagement: Seq[(String, Dependency)],
|
||||
configurations: Map[String, Seq[String]],
|
||||
properties: Map[String, String],
|
||||
profiles: Seq[Profile],
|
||||
versions: Option[Versions],
|
||||
snapshotVersioning: Option[SnapshotVersioning]
|
||||
snapshotVersioning: Option[SnapshotVersioning],
|
||||
|
||||
// Ivy-specific
|
||||
// First String is configuration
|
||||
publications: Seq[(String, Publication)]
|
||||
) {
|
||||
def moduleVersion = (module, version)
|
||||
}
|
||||
|
||||
// Maven-specific
|
||||
case class Activation(properties: Seq[(String, Option[String])])
|
||||
|
||||
// Maven-specific
|
||||
case class Profile(
|
||||
id: String,
|
||||
activeByDefault: Option[Boolean],
|
||||
|
|
@ -72,6 +85,7 @@ case class Profile(
|
|||
properties: Map[String, String]
|
||||
)
|
||||
|
||||
// Maven-specific
|
||||
case class Versions(
|
||||
latest: String,
|
||||
release: String,
|
||||
|
|
@ -90,6 +104,7 @@ object Versions {
|
|||
)
|
||||
}
|
||||
|
||||
// Maven-specific
|
||||
case class SnapshotVersion(
|
||||
classifier: String,
|
||||
extension: String,
|
||||
|
|
@ -97,6 +112,7 @@ case class SnapshotVersion(
|
|||
updated: Option[Versions.DateTime]
|
||||
)
|
||||
|
||||
// Maven-specific
|
||||
case class SnapshotVersioning(
|
||||
module: Module,
|
||||
version: String,
|
||||
|
|
@ -109,6 +125,13 @@ case class SnapshotVersioning(
|
|||
snapshotVersions: Seq[SnapshotVersion]
|
||||
)
|
||||
|
||||
// Ivy-specific
|
||||
case class Publication(
|
||||
name: String,
|
||||
`type`: String,
|
||||
ext: String
|
||||
)
|
||||
|
||||
case class Artifact(
|
||||
url: String,
|
||||
checksumUrls: Map[String, String],
|
||||
|
|
|
|||
|
|
@ -115,15 +115,25 @@ object Orders {
|
|||
}
|
||||
}
|
||||
|
||||
private def fallbackConfigIfNecessary(dep: Dependency, configs: Set[String]): Dependency =
|
||||
Parse.withFallbackConfig(dep.configuration) match {
|
||||
case Some((main, fallback)) if !configs(main) && configs(fallback) =>
|
||||
dep.copy(configuration = fallback)
|
||||
case _ =>
|
||||
dep
|
||||
}
|
||||
|
||||
/**
|
||||
* Assume all dependencies have same `module`, `version`, and `artifact`; see `minDependencies`
|
||||
* if they don't.
|
||||
*/
|
||||
def minDependenciesUnsafe(
|
||||
dependencies: Set[Dependency],
|
||||
configs: ((Module, String)) => Map[String, Seq[String]]
|
||||
configs: Map[String, Seq[String]]
|
||||
): Set[Dependency] = {
|
||||
val availableConfigs = configs.keySet
|
||||
val groupedDependencies = dependencies
|
||||
.map(fallbackConfigIfNecessary(_, availableConfigs))
|
||||
.groupBy(dep => (dep.optional, dep.configuration))
|
||||
.mapValues(deps => deps.head.copy(exclusions = deps.foldLeft(Exclusions.one)((acc, dep) => Exclusions.meet(acc, dep.exclusions))))
|
||||
.toList
|
||||
|
|
@ -132,7 +142,7 @@ object Orders {
|
|||
for {
|
||||
List(((xOpt, xScope), xDep), ((yOpt, yScope), yDep)) <- groupedDependencies.combinations(2)
|
||||
optCmp <- optionalPartialOrder.tryCompare(xOpt, yOpt).iterator
|
||||
scopeCmp <- configurationPartialOrder(configs(xDep.moduleVersion)).tryCompare(xScope, yScope).iterator
|
||||
scopeCmp <- configurationPartialOrder(configs).tryCompare(xScope, yScope).iterator
|
||||
if optCmp*scopeCmp >= 0
|
||||
exclCmp <- exclusionsPartialOrder.tryCompare(xDep.exclusions, yDep.exclusions).iterator
|
||||
if optCmp*exclCmp >= 0
|
||||
|
|
@ -156,7 +166,7 @@ object Orders {
|
|||
): Set[Dependency] = {
|
||||
dependencies
|
||||
.groupBy(_.copy(configuration = "", exclusions = Set.empty, optional = false))
|
||||
.mapValues(minDependenciesUnsafe(_, configs))
|
||||
.mapValues(deps => minDependenciesUnsafe(deps, configs(deps.head.moduleVersion)))
|
||||
.valuesIterator
|
||||
.fold(Set.empty)(_ ++ _)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package coursier.core
|
||||
|
||||
import java.util.regex.Pattern.quote
|
||||
import coursier.core.compatibility._
|
||||
|
||||
object Parse {
|
||||
|
|
@ -31,4 +32,20 @@ object Parse {
|
|||
.orElse(versionInterval(s).map(VersionConstraint.Interval))
|
||||
}
|
||||
|
||||
val fallbackConfigRegex = {
|
||||
val noPar = "([^" + quote("()") + "]*)"
|
||||
"^" + noPar + quote("(") + noPar + quote(")") + "$"
|
||||
}.r
|
||||
|
||||
def withFallbackConfig(config: String): Option[(String, String)] =
|
||||
Parse.fallbackConfigRegex.findAllMatchIn(config).toSeq match {
|
||||
case Seq(m) =>
|
||||
assert(m.groupCount == 2)
|
||||
val main = config.substring(m.start(1), m.end(1))
|
||||
val fallback = config.substring(m.start(2), m.end(2))
|
||||
Some((main, fallback))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,7 +286,18 @@ object Resolution {
|
|||
helper(extraConfigs, acc ++ configs)
|
||||
}
|
||||
|
||||
helper(Set(config), Set.empty)
|
||||
val config0 = Parse.withFallbackConfig(config) match {
|
||||
case Some((main, fallback)) =>
|
||||
if (configurations.contains(main))
|
||||
main
|
||||
else if (configurations.contains(fallback))
|
||||
fallback
|
||||
else
|
||||
main
|
||||
case None => config
|
||||
}
|
||||
|
||||
helper(Set(config0), Set.empty)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -741,6 +752,16 @@ case class Resolution(
|
|||
.artifacts(dep, proj)
|
||||
} yield artifact
|
||||
|
||||
def artifactsByDep: Seq[(Dependency, Artifact)] =
|
||||
for {
|
||||
dep <- minDependencies.toSeq
|
||||
(source, proj) <- projectCache
|
||||
.get(dep.moduleVersion)
|
||||
.toSeq
|
||||
artifact <- source
|
||||
.artifacts(dep, proj)
|
||||
} yield dep -> artifact
|
||||
|
||||
def errors: Seq[(Dependency, Seq[String])] =
|
||||
for {
|
||||
dep <- dependencies.toSeq
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
package coursier.ivy
|
||||
|
||||
import coursier.core._
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.matching.Regex
|
||||
import scalaz._
|
||||
import java.util.regex.Pattern.quote
|
||||
|
||||
object IvyRepository {
|
||||
|
||||
val optionalPartRegex = (quote("(") + "[^" + quote("()") + "]*" + quote(")")).r
|
||||
val variableRegex = (quote("[") + "[^" + quote("[()]") + "]*" + quote("]")).r
|
||||
|
||||
sealed abstract class PatternPart(val effectiveStart: Int, val effectiveEnd: Int) extends Product with Serializable {
|
||||
require(effectiveStart <= effectiveEnd)
|
||||
def start = effectiveStart
|
||||
def end = effectiveEnd
|
||||
|
||||
// FIXME Some kind of validation should be used here, to report all the missing variables,
|
||||
// not only the first one missing.
|
||||
def apply(content: String): Map[String, String] => String \/ String
|
||||
}
|
||||
object PatternPart {
|
||||
case class Literal(override val effectiveStart: Int, override val effectiveEnd: Int) extends PatternPart(effectiveStart, effectiveEnd) {
|
||||
def apply(content: String): Map[String, String] => String \/ String = {
|
||||
assert(content.length == effectiveEnd - effectiveStart)
|
||||
val matches = variableRegex.findAllMatchIn(content).toList
|
||||
|
||||
variables =>
|
||||
@tailrec
|
||||
def helper(idx: Int, matches: List[Regex.Match], b: StringBuilder): String \/ String =
|
||||
if (idx >= content.length)
|
||||
\/-(b.result())
|
||||
else {
|
||||
assert(matches.headOption.forall(_.start >= idx))
|
||||
matches.headOption.filter(_.start == idx) match {
|
||||
case Some(m) =>
|
||||
val variableName = content.substring(m.start + 1, m.end - 1)
|
||||
variables.get(variableName) match {
|
||||
case None => -\/(s"Variable not found: $variableName")
|
||||
case Some(value) =>
|
||||
b ++= value
|
||||
helper(m.end, matches.tail, b)
|
||||
}
|
||||
case None =>
|
||||
val nextIdx = matches.headOption.fold(content.length)(_.start)
|
||||
b ++= content.substring(idx, nextIdx)
|
||||
helper(nextIdx, matches, b)
|
||||
}
|
||||
}
|
||||
|
||||
helper(0, matches, new StringBuilder)
|
||||
}
|
||||
}
|
||||
case class Optional(start0: Int, end0: Int) extends PatternPart(start0 + 1, end0 - 1) {
|
||||
override def start = start0
|
||||
override def end = end0
|
||||
|
||||
def apply(content: String): Map[String, String] => String \/ String = {
|
||||
assert(content.length == effectiveEnd - effectiveStart)
|
||||
val inner = Literal(effectiveStart, effectiveEnd).apply(content)
|
||||
|
||||
variables =>
|
||||
\/-(inner(variables).fold(_ => "", x => x))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class IvyRepository(pattern: String) extends Repository {
|
||||
|
||||
import Repository._
|
||||
import IvyRepository._
|
||||
|
||||
val parts = {
|
||||
val optionalParts = optionalPartRegex.findAllMatchIn(pattern).toList.map { m =>
|
||||
PatternPart.Optional(m.start, m.end)
|
||||
}
|
||||
|
||||
val len = pattern.length
|
||||
|
||||
@tailrec
|
||||
def helper(
|
||||
idx: Int,
|
||||
opt: List[PatternPart.Optional],
|
||||
acc: List[PatternPart]
|
||||
): Vector[PatternPart] =
|
||||
if (idx >= len)
|
||||
acc.toVector.reverse
|
||||
else
|
||||
opt match {
|
||||
case Nil =>
|
||||
helper(len, Nil, PatternPart.Literal(idx, len) :: acc)
|
||||
case (opt0 @ PatternPart.Optional(start0, end0)) :: rem =>
|
||||
if (idx < start0)
|
||||
helper(start0, opt, PatternPart.Literal(idx, start0) :: acc)
|
||||
else {
|
||||
assert(idx == start0, s"idx: $idx, start0: $start0")
|
||||
helper(end0, rem, opt0 :: acc)
|
||||
}
|
||||
}
|
||||
|
||||
helper(0, optionalParts, Nil)
|
||||
}
|
||||
|
||||
assert(pattern.isEmpty == parts.isEmpty)
|
||||
if (pattern.nonEmpty) {
|
||||
for ((a, b) <- parts.zip(parts.tail))
|
||||
assert(a.end == b.start)
|
||||
assert(parts.head.start == 0)
|
||||
assert(parts.last.end == pattern.length)
|
||||
}
|
||||
|
||||
private val substituteHelpers = parts.map { part =>
|
||||
part(pattern.substring(part.effectiveStart, part.effectiveEnd))
|
||||
}
|
||||
|
||||
def substitute(variables: Map[String, String]): String \/ String =
|
||||
substituteHelpers.foldLeft[String \/ String](\/-("")) {
|
||||
case (acc0, helper) =>
|
||||
for {
|
||||
acc <- acc0
|
||||
s <- helper(variables)
|
||||
} yield acc + s
|
||||
}
|
||||
|
||||
// If attributes are added to `Module`, they should be added here
|
||||
// See http://ant.apache.org/ivy/history/latest-milestone/concept.html for a
|
||||
// list of variables that should be supported.
|
||||
// Some are missing (branch, conf, originalName).
|
||||
private def variables(
|
||||
org: String,
|
||||
name: String,
|
||||
version: String,
|
||||
`type`: String,
|
||||
artifact: String,
|
||||
ext: String
|
||||
) =
|
||||
Map(
|
||||
"organization" -> org,
|
||||
"organisation" -> org,
|
||||
"orgPath" -> org.replace('.', '/'),
|
||||
"module" -> name,
|
||||
"revision" -> version,
|
||||
"type" -> `type`,
|
||||
"artifact" -> artifact,
|
||||
"ext" -> ext
|
||||
)
|
||||
|
||||
|
||||
val source: Artifact.Source = new Artifact.Source {
|
||||
def artifacts(dependency: Dependency, project: Project) =
|
||||
project
|
||||
.publications
|
||||
.collect { case (conf, p) if conf == "*" || conf == dependency.configuration => p }
|
||||
.flatMap { p =>
|
||||
substitute(variables(
|
||||
dependency.module.organization,
|
||||
dependency.module.name,
|
||||
dependency.version,
|
||||
p.`type`,
|
||||
p.name,
|
||||
p.ext
|
||||
)).toList.map(p -> _)
|
||||
}
|
||||
.map { case (p, url) =>
|
||||
Artifact(
|
||||
url,
|
||||
Map.empty,
|
||||
Map.empty,
|
||||
Attributes(p.`type`, p.ext)
|
||||
)
|
||||
.withDefaultChecksums
|
||||
.withDefaultSignature
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def find[F[_]](
|
||||
module: Module,
|
||||
version: String,
|
||||
fetch: Repository.Fetch[F]
|
||||
)(implicit
|
||||
F: Monad[F]
|
||||
): EitherT[F, String, (Artifact.Source, Project)] = {
|
||||
|
||||
val eitherArtifact: String \/ Artifact =
|
||||
for {
|
||||
url <- substitute(variables(module.organization, module.name, version, "ivy", "ivy", "xml"))
|
||||
} yield
|
||||
Artifact(
|
||||
url,
|
||||
Map.empty,
|
||||
Map.empty,
|
||||
Attributes("ivy", "")
|
||||
)
|
||||
.withDefaultChecksums
|
||||
.withDefaultSignature
|
||||
|
||||
for {
|
||||
artifact <- EitherT(F.point(eitherArtifact))
|
||||
ivy <- fetch(artifact)
|
||||
proj <- EitherT(F.point {
|
||||
for {
|
||||
xml <- \/.fromEither(compatibility.xmlParse(ivy))
|
||||
_ <- if (xml.label == "ivy-module") \/-(()) else -\/("Module definition not found")
|
||||
proj <- IvyXml.project(xml)
|
||||
} yield proj
|
||||
})
|
||||
} yield (source, proj)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package coursier.ivy
|
||||
|
||||
import coursier.core._
|
||||
import coursier.util.Xml._
|
||||
|
||||
import scalaz.{ Node => _, _ }, Scalaz._
|
||||
|
||||
object IvyXml {
|
||||
|
||||
private def info(node: Node): String \/ (Module, String) =
|
||||
for {
|
||||
org <- node.attribute("organisation")
|
||||
name <- node.attribute("module")
|
||||
version <- node.attribute("revision")
|
||||
} yield (Module(org, name), version)
|
||||
|
||||
// FIXME Errors are ignored here
|
||||
private def configurations(node: Node): Seq[(String, Seq[String])] =
|
||||
node.children
|
||||
.filter(_.label == "conf")
|
||||
.flatMap { node =>
|
||||
node.attribute("name").toOption.toSeq.map(_ -> node)
|
||||
}
|
||||
.map { case (name, node) =>
|
||||
name -> node.attribute("extends").toOption.toSeq.flatMap(_.split(','))
|
||||
}
|
||||
|
||||
// FIXME Errors ignored as above - warnings should be reported at least for anything suspicious
|
||||
private def dependencies(node: Node): Seq[(String, Dependency)] =
|
||||
node.children
|
||||
.filter(_.label == "dependency")
|
||||
.flatMap { node =>
|
||||
// artifact and include sub-nodes are ignored here
|
||||
|
||||
val excludes = node.children
|
||||
.filter(_.label == "exclude")
|
||||
.flatMap { node0 =>
|
||||
val org = node.attribute("org").getOrElse("*")
|
||||
val name = node.attribute("module").orElse(node.attribute("name")).getOrElse("*")
|
||||
val confs = node.attribute("conf").toOption.fold(Seq("*"))(_.split(','))
|
||||
confs.map(_ -> (org, name))
|
||||
}
|
||||
.groupBy { case (conf, _) => conf }
|
||||
.map { case (conf, l) => conf -> l.map { case (_, e) => e }.toSet }
|
||||
|
||||
val allConfsExcludes = excludes.getOrElse("*", Set.empty)
|
||||
|
||||
for {
|
||||
org <- node.attribute("org").toOption.toSeq
|
||||
name <- node.attribute("name").toOption.toSeq
|
||||
version <- node.attribute("rev").toOption.toSeq
|
||||
rawConf <- node.attribute("conf").toOption.toSeq
|
||||
(fromConf, toConf) <- rawConf.split(',').toSeq.map(_.split("->", 2)).collect {
|
||||
case Array(from, to) => from -> to
|
||||
}
|
||||
} yield fromConf -> Dependency(
|
||||
Module(org, name),
|
||||
version,
|
||||
toConf,
|
||||
allConfsExcludes ++ excludes.getOrElse(fromConf, Set.empty),
|
||||
Attributes("jar", ""), // should come from possible artifact nodes
|
||||
optional = false
|
||||
)
|
||||
}
|
||||
|
||||
private def publications(node: Node): Map[String, Seq[Publication]] =
|
||||
node.children
|
||||
.filter(_.label == "artifact")
|
||||
.flatMap { node0 =>
|
||||
val name = node.attribute("name").getOrElse("")
|
||||
val type0 = node.attribute("type").getOrElse("jar")
|
||||
val ext = node.attribute("ext").getOrElse(type0)
|
||||
val confs = node.attribute("conf").toOption.fold(Seq("*"))(_.split(','))
|
||||
confs.map(_ -> Publication(name, type0, ext))
|
||||
}
|
||||
.groupBy { case (conf, _) => conf }
|
||||
.map { case (conf, l) => conf -> l.map { case (_, p) => p } }
|
||||
|
||||
def project(node: Node): String \/ Project =
|
||||
for {
|
||||
infoNode <- node.children
|
||||
.find(_.label == "info")
|
||||
.toRightDisjunction("Info not found")
|
||||
|
||||
(module, version) <- info(infoNode)
|
||||
|
||||
dependenciesNodeOpt = node.children
|
||||
.find(_.label == "dependencies")
|
||||
|
||||
dependencies0 = dependenciesNodeOpt.map(dependencies).getOrElse(Nil)
|
||||
|
||||
configurationsNodeOpt = node.children
|
||||
.find(_.label == "configurations")
|
||||
|
||||
configurationsOpt = configurationsNodeOpt.map(configurations)
|
||||
|
||||
configurations0 = configurationsOpt.getOrElse(Seq("default" -> Seq.empty[String]))
|
||||
|
||||
publicationsNodeOpt = node.children
|
||||
.find(_.label == "publications")
|
||||
|
||||
publicationsOpt = publicationsNodeOpt.map(publications)
|
||||
|
||||
} yield
|
||||
Project(
|
||||
module,
|
||||
version,
|
||||
dependencies0,
|
||||
configurations0.toMap,
|
||||
None,
|
||||
Nil,
|
||||
Map.empty,
|
||||
Nil,
|
||||
None,
|
||||
None,
|
||||
if (publicationsOpt.isEmpty)
|
||||
// no publications node -> default JAR artifact
|
||||
Seq("*" -> Publication(module.name, "jar", "jar"))
|
||||
else
|
||||
// publications node is there -> only its content (if it is empty, no artifacts,
|
||||
// as per the Ivy manual)
|
||||
configurations0.flatMap { case (conf, _) =>
|
||||
publicationsOpt.flatMap(_.get(conf)).getOrElse(Nil).map(conf -> _)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ object MavenRepository {
|
|||
|
||||
|
||||
val defaultConfigurations = Map(
|
||||
"compile" -> Seq.empty,
|
||||
"runtime" -> Seq("compile"),
|
||||
"test" -> Seq("runtime")
|
||||
)
|
||||
|
|
@ -106,7 +107,7 @@ case class MavenRepository(
|
|||
Attributes("pom", "")
|
||||
)
|
||||
.withDefaultChecksums
|
||||
.withDefaultChecksums
|
||||
.withDefaultSignature
|
||||
|
||||
Some(artifact)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,21 +7,6 @@ import scalaz._
|
|||
object Pom {
|
||||
import coursier.util.Xml._
|
||||
|
||||
object Text {
|
||||
def unapply(n: Node): Option[String] =
|
||||
if (n.isText) Some(n.textContent)
|
||||
else None
|
||||
}
|
||||
|
||||
private def text(elem: Node, label: String, description: String) = {
|
||||
import Scalaz.ToOptionOpsFromOption
|
||||
|
||||
elem.child
|
||||
.find(_.label == label)
|
||||
.flatMap(_.child.collectFirst{case Text(t) => t})
|
||||
.toRightDisjunction(s"$description not found")
|
||||
}
|
||||
|
||||
def property(elem: Node): String \/ (String, String) = {
|
||||
// Not matching with Text, which fails on scala-js if the property value has xml comments
|
||||
if (elem.isElement) \/-(elem.label -> elem.textContent)
|
||||
|
|
@ -53,9 +38,9 @@ object Pom {
|
|||
scopeOpt = text(node, "scope", "").toOption
|
||||
typeOpt = text(node, "type", "").toOption
|
||||
classifierOpt = text(node, "classifier", "").toOption
|
||||
xmlExclusions = node.child
|
||||
xmlExclusions = node.children
|
||||
.find(_.label == "exclusions")
|
||||
.map(_.child.filter(_.label == "exclusion"))
|
||||
.map(_.children.filter(_.label == "exclusion"))
|
||||
.getOrElse(Seq.empty)
|
||||
exclusions <- {
|
||||
import Scalaz._
|
||||
|
|
@ -66,8 +51,8 @@ object Pom {
|
|||
mod,
|
||||
version0,
|
||||
"",
|
||||
Attributes(typeOpt getOrElse defaultType, classifierOpt getOrElse defaultClassifier),
|
||||
exclusions.map(mod => (mod.organization, mod.name)).toSet,
|
||||
Attributes(typeOpt getOrElse defaultType, classifierOpt getOrElse defaultClassifier),
|
||||
optional
|
||||
)
|
||||
}
|
||||
|
|
@ -80,7 +65,7 @@ object Pom {
|
|||
case _ => None
|
||||
}
|
||||
|
||||
val properties = node.child
|
||||
val properties = node.children
|
||||
.filter(_.label == "property")
|
||||
.flatMap{ p =>
|
||||
for{
|
||||
|
|
@ -97,28 +82,28 @@ object Pom {
|
|||
|
||||
val id = text(node, "id", "Profile ID").getOrElse("")
|
||||
|
||||
val xmlActivationOpt = node.child
|
||||
val xmlActivationOpt = node.children
|
||||
.find(_.label == "activation")
|
||||
val (activeByDefault, activation) = xmlActivationOpt.fold((Option.empty[Boolean], Activation(Nil)))(profileActivation)
|
||||
|
||||
val xmlDeps = node.child
|
||||
val xmlDeps = node.children
|
||||
.find(_.label == "dependencies")
|
||||
.map(_.child.filter(_.label == "dependency"))
|
||||
.map(_.children.filter(_.label == "dependency"))
|
||||
.getOrElse(Seq.empty)
|
||||
|
||||
for {
|
||||
deps <- xmlDeps.toList.traverseU(dependency)
|
||||
|
||||
xmlDepMgmts = node.child
|
||||
xmlDepMgmts = node.children
|
||||
.find(_.label == "dependencyManagement")
|
||||
.flatMap(_.child.find(_.label == "dependencies"))
|
||||
.map(_.child.filter(_.label == "dependency"))
|
||||
.flatMap(_.children.find(_.label == "dependencies"))
|
||||
.map(_.children.filter(_.label == "dependency"))
|
||||
.getOrElse(Seq.empty)
|
||||
depMgmts <- xmlDepMgmts.toList.traverseU(dependency)
|
||||
|
||||
xmlProperties = node.child
|
||||
xmlProperties = node.children
|
||||
.find(_.label == "properties")
|
||||
.map(_.child.collect{case elem if elem.isElement => elem})
|
||||
.map(_.children.collect{case elem if elem.isElement => elem})
|
||||
.getOrElse(Seq.empty)
|
||||
|
||||
properties <- {
|
||||
|
|
@ -136,7 +121,7 @@ object Pom {
|
|||
projModule <- module(pom, groupIdIsOptional = true)
|
||||
projVersion = readVersion(pom)
|
||||
|
||||
parentOpt = pom.child
|
||||
parentOpt = pom.children
|
||||
.find(_.label == "parent")
|
||||
parentModuleOpt <- parentOpt
|
||||
.map(module(_).map(Some(_)))
|
||||
|
|
@ -144,16 +129,16 @@ object Pom {
|
|||
parentVersionOpt = parentOpt
|
||||
.map(readVersion)
|
||||
|
||||
xmlDeps = pom.child
|
||||
xmlDeps = pom.children
|
||||
.find(_.label == "dependencies")
|
||||
.map(_.child.filter(_.label == "dependency"))
|
||||
.map(_.children.filter(_.label == "dependency"))
|
||||
.getOrElse(Seq.empty)
|
||||
deps <- xmlDeps.toList.traverseU(dependency)
|
||||
|
||||
xmlDepMgmts = pom.child
|
||||
xmlDepMgmts = pom.children
|
||||
.find(_.label == "dependencyManagement")
|
||||
.flatMap(_.child.find(_.label == "dependencies"))
|
||||
.map(_.child.filter(_.label == "dependency"))
|
||||
.flatMap(_.children.find(_.label == "dependencies"))
|
||||
.map(_.children.filter(_.label == "dependency"))
|
||||
.getOrElse(Seq.empty)
|
||||
depMgmts <- xmlDepMgmts.toList.traverseU(dependency)
|
||||
|
||||
|
|
@ -171,15 +156,15 @@ object Pom {
|
|||
.map(mod => if (mod.organization.isEmpty) -\/("Parent organization missing") else \/-(()))
|
||||
.getOrElse(\/-(()))
|
||||
|
||||
xmlProperties = pom.child
|
||||
xmlProperties = pom.children
|
||||
.find(_.label == "properties")
|
||||
.map(_.child.collect{case elem if elem.isElement => elem})
|
||||
.map(_.children.collect{case elem if elem.isElement => elem})
|
||||
.getOrElse(Seq.empty)
|
||||
properties <- xmlProperties.toList.traverseU(property)
|
||||
|
||||
xmlProfiles = pom.child
|
||||
xmlProfiles = pom.children
|
||||
.find(_.label == "profiles")
|
||||
.map(_.child.filter(_.label == "profile"))
|
||||
.map(_.children.filter(_.label == "profile"))
|
||||
.getOrElse(Seq.empty)
|
||||
profiles <- xmlProfiles.toList.traverseU(profile)
|
||||
|
||||
|
|
@ -187,13 +172,14 @@ object Pom {
|
|||
projModule.copy(organization = groupId),
|
||||
version,
|
||||
deps,
|
||||
Map.empty,
|
||||
parentModuleOpt.map((_, parentVersionOpt.getOrElse(""))),
|
||||
depMgmts,
|
||||
Map.empty,
|
||||
properties.toMap,
|
||||
profiles,
|
||||
None,
|
||||
None
|
||||
None,
|
||||
Nil
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +203,7 @@ object Pom {
|
|||
organization <- text(node, "groupId", "Organization") // Ignored
|
||||
name <- text(node, "artifactId", "Name") // Ignored
|
||||
|
||||
xmlVersioning <- node.child
|
||||
xmlVersioning <- node.children
|
||||
.find(_.label == "versioning")
|
||||
.toRightDisjunction("Versioning info not found in metadata")
|
||||
|
||||
|
|
@ -226,9 +212,9 @@ object Pom {
|
|||
release = text(xmlVersioning, "release", "Release version")
|
||||
.getOrElse("")
|
||||
|
||||
versionsOpt = xmlVersioning.child
|
||||
versionsOpt = xmlVersioning.children
|
||||
.find(_.label == "versions")
|
||||
.map(_.child.filter(_.label == "version").flatMap(_.child.collectFirst{case Text(t) => t}))
|
||||
.map(_.children.filter(_.label == "version").flatMap(_.children.collectFirst{case Text(t) => t}))
|
||||
|
||||
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
|
||||
.toOption
|
||||
|
|
@ -268,7 +254,7 @@ object Pom {
|
|||
name <- text(node, "artifactId", "Name")
|
||||
version = readVersion(node)
|
||||
|
||||
xmlVersioning <- node.child
|
||||
xmlVersioning <- node.children
|
||||
.find(_.label == "versioning")
|
||||
.toRightDisjunction("Versioning info not found in metadata")
|
||||
|
||||
|
|
@ -277,15 +263,15 @@ object Pom {
|
|||
release = text(xmlVersioning, "release", "Release version")
|
||||
.getOrElse("")
|
||||
|
||||
versionsOpt = xmlVersioning.child
|
||||
versionsOpt = xmlVersioning.children
|
||||
.find(_.label == "versions")
|
||||
.map(_.child.filter(_.label == "version").flatMap(_.child.collectFirst{case Text(t) => t}))
|
||||
.map(_.children.filter(_.label == "version").flatMap(_.children.collectFirst{case Text(t) => t}))
|
||||
|
||||
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
|
||||
.toOption
|
||||
.flatMap(parseDateTime)
|
||||
|
||||
xmlSnapshotOpt = xmlVersioning.child
|
||||
xmlSnapshotOpt = xmlVersioning.children
|
||||
.find(_.label == "snapshot")
|
||||
|
||||
timestamp = xmlSnapshotOpt
|
||||
|
|
@ -313,9 +299,9 @@ object Pom {
|
|||
case "false" => false
|
||||
}
|
||||
|
||||
xmlSnapshotVersions = xmlVersioning.child
|
||||
xmlSnapshotVersions = xmlVersioning.children
|
||||
.find(_.label == "snapshotVersions")
|
||||
.map(_.child.filter(_.label == "snapshotVersion"))
|
||||
.map(_.children.filter(_.label == "snapshotVersion"))
|
||||
.getOrElse(Seq.empty)
|
||||
snapshotVersions <- xmlSnapshotVersions
|
||||
.toList
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ package object coursier {
|
|||
module,
|
||||
version,
|
||||
configuration,
|
||||
attributes,
|
||||
exclusions,
|
||||
attributes,
|
||||
optional
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
package coursier.util
|
||||
|
||||
import scalaz.{\/-, -\/, \/, Scalaz}
|
||||
|
||||
object Xml {
|
||||
|
||||
/** A representation of an XML node/document, with different implementations on JVM and JS */
|
||||
trait Node {
|
||||
def label: String
|
||||
def child: Seq[Node]
|
||||
def attributes: Seq[(String, String)]
|
||||
def children: Seq[Node]
|
||||
def isText: Boolean
|
||||
def textContent: String
|
||||
def isElement: Boolean
|
||||
|
||||
lazy val attributesMap = attributes.toMap
|
||||
def attribute(name: String): String \/ String =
|
||||
attributesMap.get(name) match {
|
||||
case None => -\/(s"Missing attribute $name")
|
||||
case Some(value) => \/-(value)
|
||||
}
|
||||
}
|
||||
|
||||
object Node {
|
||||
|
|
@ -16,10 +26,26 @@ object Xml {
|
|||
new Node {
|
||||
val isText = false
|
||||
val isElement = false
|
||||
val child = Nil
|
||||
val children = Nil
|
||||
val label = ""
|
||||
val attributes = Nil
|
||||
val textContent = ""
|
||||
}
|
||||
}
|
||||
|
||||
object Text {
|
||||
def unapply(n: Node): Option[String] =
|
||||
if (n.isText) Some(n.textContent)
|
||||
else None
|
||||
}
|
||||
|
||||
def text(elem: Node, label: String, description: String) = {
|
||||
import Scalaz.ToOptionOpsFromOption
|
||||
|
||||
elem.children
|
||||
.find(_.label == label)
|
||||
.flatMap(_.children.collectFirst{case Text(t) => t})
|
||||
.toRightDisjunction(s"$description not found")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ object PomParsingTests extends TestSuite {
|
|||
val node = parsed.right.get
|
||||
assert(node.label == "properties")
|
||||
|
||||
val children = node.child.collect{case elem if elem.isElement => elem}
|
||||
val children = node.children.collect{case elem if elem.isElement => elem}
|
||||
val props0 = children.toList.traverseU(Pom.property)
|
||||
|
||||
assert(props0.isRight)
|
||||
|
|
|
|||
|
|
@ -13,27 +13,50 @@ package object test {
|
|||
core.Activation(properties)
|
||||
}
|
||||
|
||||
def apply(id: String,
|
||||
activeByDefault: Option[Boolean] = None,
|
||||
activation: Activation = Activation(),
|
||||
dependencies: Seq[(String, Dependency)] = Nil,
|
||||
dependencyManagement: Seq[(String, Dependency)] = Nil,
|
||||
properties: Map[String, String] = Map.empty) =
|
||||
core.Profile(id, activeByDefault, activation, dependencies, dependencyManagement, properties)
|
||||
def apply(
|
||||
id: String,
|
||||
activeByDefault: Option[Boolean] = None,
|
||||
activation: Activation = Activation(),
|
||||
dependencies: Seq[(String, Dependency)] = Nil,
|
||||
dependencyManagement: Seq[(String, Dependency)] = Nil,
|
||||
properties: Map[String, String] = Map.empty
|
||||
) =
|
||||
core.Profile(
|
||||
id,
|
||||
activeByDefault,
|
||||
activation,
|
||||
dependencies,
|
||||
dependencyManagement,
|
||||
properties
|
||||
)
|
||||
}
|
||||
|
||||
object Project {
|
||||
def apply(module: Module,
|
||||
version: String,
|
||||
dependencies: Seq[(String, Dependency)] = Seq.empty,
|
||||
parent: Option[ModuleVersion] = None,
|
||||
dependencyManagement: Seq[(String, Dependency)] = Seq.empty,
|
||||
configurations: Map[String, Seq[String]] = Map.empty,
|
||||
properties: Map[String, String] = Map.empty,
|
||||
profiles: Seq[Profile] = Seq.empty,
|
||||
versions: Option[core.Versions] = None,
|
||||
snapshotVersioning: Option[core.SnapshotVersioning] = None
|
||||
): Project =
|
||||
core.Project(module, version, dependencies, parent, dependencyManagement, configurations, properties, profiles, versions, snapshotVersioning)
|
||||
def apply(
|
||||
module: Module,
|
||||
version: String,
|
||||
dependencies: Seq[(String, Dependency)] = Seq.empty,
|
||||
parent: Option[ModuleVersion] = None,
|
||||
dependencyManagement: Seq[(String, Dependency)] = Seq.empty,
|
||||
configurations: Map[String, Seq[String]] = Map.empty,
|
||||
properties: Map[String, String] = Map.empty,
|
||||
profiles: Seq[Profile] = Seq.empty,
|
||||
versions: Option[core.Versions] = None,
|
||||
snapshotVersioning: Option[core.SnapshotVersioning] = None,
|
||||
publications: Seq[(String, core.Publication)] = Nil
|
||||
): Project =
|
||||
core.Project(
|
||||
module,
|
||||
version,
|
||||
dependencies,
|
||||
configurations,
|
||||
parent,
|
||||
dependencyManagement,
|
||||
properties,
|
||||
profiles,
|
||||
versions,
|
||||
snapshotVersioning,
|
||||
publications
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue