Snapshot versioning

Fixes https://github.com/alexarchambault/coursier/issues/61
This commit is contained in:
Alexandre Archambault 2015-07-07 12:29:24 +02:00
parent f2331b388e
commit 0418c790b6
5 changed files with 279 additions and 23 deletions

View File

@ -49,7 +49,15 @@ case class MavenRepository(
val url = new URL(urlStr)
def log = Task(logger.foreach(_.downloading(urlStr)))
def get = MavenRepository.readFully(url.openStream())
def get = {
val conn = url.openConnection()
// Dummy user-agent instead of the default "Java/...",
// so that we are not returned incomplete/erroneous metadata
// (Maven 2 compatibility? - happens for snapshot versioning metadata,
// this is SO FUCKING CRAZY)
conn.setRequestProperty("User-Agent", "")
MavenRepository.readFully(conn.getInputStream())
}
log.flatMap(_ => get)
}

View File

@ -56,7 +56,8 @@ case class Project(
dependencyManagement: Seq[Dependency],
properties: Map[String, String],
profiles: Seq[Profile],
versions: Option[Versions]
versions: Option[Versions],
snapshotVersioning: Option[SnapshotVersioning]
) {
def moduleVersion = (module, version)
}
@ -99,6 +100,25 @@ object Versions {
)
}
case class SnapshotVersion(
classifier: String,
extension: String,
value: String,
updated: Option[Versions.DateTime]
)
case class SnapshotVersioning(
module: Module,
version: String,
latest: String,
release: String,
timestamp: String,
buildNumber: Option[Int],
localCopy: Option[Boolean],
lastUpdated: Option[Versions.DateTime],
snapshotVersions: Seq[SnapshotVersion]
)
case class Artifact(
url: String,
checksumUrls: Map[String, String],

View File

@ -101,6 +101,7 @@ object Repository {
case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
import Repository._
import BaseMavenRepository._
def artifacts(
dependency: Dependency,
@ -108,7 +109,7 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
): Seq[Artifact] = {
def ivyLikePath0(subDir: String, baseSuffix: String, ext: String) =
BaseMavenRepository.ivyLikePath(
ivyLikePath(
dependency.module.organization,
dependency.module.name,
project.version,
@ -120,12 +121,20 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
val path =
if (ivyLike)
ivyLikePath0(dependency.attributes.`type` + "s", "", dependency.attributes.`type`)
else
else {
val versioning =
project
.snapshotVersioning
.flatMap(versioning =>
mavenVersioning(versioning, dependency.attributes.classifier, dependency.attributes.`type`)
)
dependency.module.organization.split('.').toSeq ++ Seq(
dependency.module.name,
project.version,
s"${dependency.module.name}-${project.version}${Some(dependency.attributes.classifier).filter(_.nonEmpty).map("-"+_).mkString}.${dependency.attributes.`type`}"
s"${dependency.module.name}-${versioning getOrElse project.version}${Some(dependency.attributes.classifier).filter(_.nonEmpty).map("-"+_).mkString}.${dependency.attributes.`type`}"
)
}
var artifact =
Artifact(
@ -139,6 +148,9 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
if (dependency.attributes.`type` == "jar") {
artifact = artifact.withDefaultSignature
// FIXME Snapshot versioning of sources and javadoc is not taken into account here.
// Will be ok if it's the same as the main JAR though.
artifact =
if (ivyLike) {
val srcPath = root + ivyLikePath0("srcs", "-sources", "jar").mkString("/")
@ -181,6 +193,17 @@ object BaseMavenRepository {
s"$name$baseSuffix.$ext"
)
def mavenVersioning(
snapshotVersioning: SnapshotVersioning,
classifier: String,
extension: String
): Option[String] =
snapshotVersioning
.snapshotVersions
.find(v => v.classifier == classifier && v.extension == extension)
.map(_.value)
.filter(_.nonEmpty)
}
abstract class BaseMavenRepository(
@ -198,16 +221,27 @@ abstract class BaseMavenRepository(
val source = MavenSource(root, ivyLike)
def projectArtifact(module: Module, version: String): Artifact = {
def projectArtifact(
module: Module,
version: String,
versioningValue: Option[String]
): Artifact = {
val path = (
if (ivyLike)
ivyLikePath(module.organization, module.name, version, "poms", "", "pom")
ivyLikePath(
module.organization,
module.name,
versioningValue getOrElse version,
"poms",
"",
"pom"
)
else
module.organization.split('.').toSeq ++ Seq(
module.name,
version,
s"${module.name}-$version.pom"
s"${module.name}-${versioningValue getOrElse version}.pom"
)
) .map(encodeURIComponent)
@ -242,6 +276,32 @@ abstract class BaseMavenRepository(
Some(artifact)
}
def snapshotVersioningArtifact(
module: Module,
version: String
): Option[Artifact] =
if (ivyLike) None
else {
val path = (
module.organization.split('.').toSeq ++ Seq(
module.name,
version,
"maven-metadata.xml"
)
) .map(encodeURIComponent)
val artifact =
Artifact(
path.mkString("/"),
Map.empty,
Map.empty,
Attributes("pom", "")
)
.withDefaultChecksums
Some(artifact)
}
def versions(
module: Module,
cachePolicy: CachePolicy = CachePolicy.Default
@ -265,14 +325,80 @@ abstract class BaseMavenRepository(
)
}
def snapshotVersioning(
module: Module,
version: String,
cachePolicy: CachePolicy = CachePolicy.Default
): EitherT[Task, String, SnapshotVersioning] = {
EitherT(
snapshotVersioningArtifact(module, version) match {
case None => Task.now(-\/("Not supported"))
case Some(artifact) =>
fetch(artifact, cachePolicy)
.run
.map(eitherStr =>
for {
str <- eitherStr
xml <- \/.fromEither(compatibility.xmlParse(str))
_ <- if (xml.label == "metadata") \/-(()) else -\/("Metadata not found")
snapshotVersioning <- Xml.snapshotVersioning(xml)
} yield snapshotVersioning
)
}
)
}
def findNoInterval(
module: Module,
version: String,
cachePolicy: CachePolicy
): EitherT[Task, String, Project] =
EitherT{
def withSnapshotVersioning =
snapshotVersioning(module, version, cachePolicy)
.flatMap { snapshotVersioning =>
val versioningOption =
mavenVersioning(snapshotVersioning, "", "jar")
.orElse(mavenVersioning(snapshotVersioning, "", ""))
versioningOption match {
case None =>
EitherT[Task, String, Project](
Task.now(-\/("No snapshot versioning value found"))
)
case versioning @ Some(_) =>
findVersioning(module, version, versioning, cachePolicy)
.map(_.copy(snapshotVersioning = Some(snapshotVersioning)))
}
}
findVersioning(module, version, None, cachePolicy)
.run
.flatMap{ eitherProj =>
if (eitherProj.isLeft)
withSnapshotVersioning
.run
.map(eitherProj0 =>
if (eitherProj0.isLeft)
eitherProj
else
eitherProj0
)
else
Task.now(eitherProj)
}
}
def findVersioning(
module: Module,
version: String,
versioningValue: Option[String],
cachePolicy: CachePolicy
): EitherT[Task, String, Project] = {
EitherT {
fetch(projectArtifact(module, version), cachePolicy)
fetch(projectArtifact(module, version, versioningValue), cachePolicy)
.run
.map(eitherStr =>
for {

View File

@ -210,10 +210,24 @@ object Xml {
depMgmts,
properties.toMap,
profiles,
None,
None
)
}
def parseDateTime(s: String): Option[Versions.DateTime] =
if (s.length == 14 && s.forall(_.isDigit))
Some(Versions.DateTime(
s.substring(0, 4).toInt,
s.substring(4, 6).toInt,
s.substring(6, 8).toInt,
s.substring(8, 10).toInt,
s.substring(10, 12).toInt,
s.substring(12, 14).toInt
))
else
None
def versions(node: Node): String \/ Versions = {
import Scalaz._
@ -230,24 +244,111 @@ object Xml {
release = text(xmlVersioning, "release", "Release version")
.getOrElse("")
versions <- xmlVersioning.child
versionsOpt = xmlVersioning.child
.find(_.label == "versions")
.map(_.child.filter(_.label == "version").flatMap(_.child.collectFirst{case Text(t) => t}))
.toRightDisjunction("Version list not found in metadata")
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
.toOption
.filter(s => s.length == 14 && s.forall(_.isDigit))
.map(s => Versions.DateTime(
s.substring(0, 4).toInt,
s.substring(4, 6).toInt,
s.substring(6, 8).toInt,
s.substring(8, 10).toInt,
s.substring(10, 12).toInt,
s.substring(12, 14).toInt
))
.flatMap(parseDateTime)
} yield Versions(latest, release, versions.toList, lastUpdatedOpt)
} yield Versions(latest, release, versionsOpt.map(_.toList).getOrElse(Nil), lastUpdatedOpt)
}
def snapshotVersion(node: Node): String \/ SnapshotVersion = {
def textOrEmpty(name: String, desc: String) =
text(node, name, desc)
.toOption
.getOrElse("")
val classifier = textOrEmpty("classifier", "Classifier")
val ext = textOrEmpty("extension", "Extensions")
val value = textOrEmpty("value", "Value")
val updatedOpt = text(node, "updated", "Updated")
.toOption
.flatMap(parseDateTime)
\/-(SnapshotVersion(
classifier,
ext,
value,
updatedOpt
))
}
def snapshotVersioning(node: Node): String \/ SnapshotVersioning = {
import Scalaz._
// FIXME Quite similar to Versions above
for {
organization <- text(node, "groupId", "Organization")
name <- text(node, "artifactId", "Name")
version = readVersion(node)
xmlVersioning <- node.child
.find(_.label == "versioning")
.toRightDisjunction("Versioning info not found in metadata")
latest = text(xmlVersioning, "latest", "Latest version")
.getOrElse("")
release = text(xmlVersioning, "release", "Release version")
.getOrElse("")
versionsOpt = xmlVersioning.child
.find(_.label == "versions")
.map(_.child.filter(_.label == "version").flatMap(_.child.collectFirst{case Text(t) => t}))
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
.toOption
.flatMap(parseDateTime)
xmlSnapshotOpt = xmlVersioning.child
.find(_.label == "snapshot")
timestamp = xmlSnapshotOpt
.flatMap(
text(_, "timestamp", "Snapshot timestamp")
.toOption
)
.getOrElse("")
buildNumber = xmlSnapshotOpt
.flatMap(
text(_, "buildNumber", "Snapshot build number")
.toOption
)
.filter(s => s.nonEmpty && s.forall(_.isDigit))
.map(_.toInt)
localCopy = xmlSnapshotOpt
.flatMap(
text(_, "localCopy", "Snapshot local copy")
.toOption
)
.collect{
case "true" => true
case "false" => false
}
xmlSnapshotVersions = xmlVersioning.child
.find(_.label == "snapshotVersions")
.map(_.child.filter(_.label == "snapshotVersion"))
.getOrElse(Seq.empty)
snapshotVersions <- xmlSnapshotVersions
.toList
.traverseU(snapshotVersion)
} yield SnapshotVersioning(
Module(organization, name),
version,
latest,
release,
timestamp,
buildNumber,
localCopy,
lastUpdatedOpt,
snapshotVersions
)
}
}

View File

@ -30,7 +30,8 @@ package object test {
dependencyManagement: Seq[Dependency] = Seq.empty,
properties: Map[String, String] = Map.empty,
profiles: Seq[Profile] = Seq.empty,
versions: Option[core.Versions] = None): Project =
core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles, versions)
versions: Option[core.Versions] = None,
snapshotVersioning: Option[core.SnapshotVersioning] = None): Project =
core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles, versions, snapshotVersioning)
}
}