mirror of https://github.com/sbt/sbt.git
Snapshot versioning
Fixes https://github.com/alexarchambault/coursier/issues/61
This commit is contained in:
parent
f2331b388e
commit
0418c790b6
|
|
@ -49,7 +49,15 @@ case class MavenRepository(
|
||||||
val url = new URL(urlStr)
|
val url = new URL(urlStr)
|
||||||
|
|
||||||
def log = Task(logger.foreach(_.downloading(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)
|
log.flatMap(_ => get)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,8 @@ case class Project(
|
||||||
dependencyManagement: Seq[Dependency],
|
dependencyManagement: Seq[Dependency],
|
||||||
properties: Map[String, String],
|
properties: Map[String, String],
|
||||||
profiles: Seq[Profile],
|
profiles: Seq[Profile],
|
||||||
versions: Option[Versions]
|
versions: Option[Versions],
|
||||||
|
snapshotVersioning: Option[SnapshotVersioning]
|
||||||
) {
|
) {
|
||||||
def moduleVersion = (module, version)
|
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(
|
case class Artifact(
|
||||||
url: String,
|
url: String,
|
||||||
checksumUrls: Map[String, String],
|
checksumUrls: Map[String, String],
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ object Repository {
|
||||||
|
|
||||||
case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
|
case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
|
||||||
import Repository._
|
import Repository._
|
||||||
|
import BaseMavenRepository._
|
||||||
|
|
||||||
def artifacts(
|
def artifacts(
|
||||||
dependency: Dependency,
|
dependency: Dependency,
|
||||||
|
|
@ -108,7 +109,7 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
|
||||||
): Seq[Artifact] = {
|
): Seq[Artifact] = {
|
||||||
|
|
||||||
def ivyLikePath0(subDir: String, baseSuffix: String, ext: String) =
|
def ivyLikePath0(subDir: String, baseSuffix: String, ext: String) =
|
||||||
BaseMavenRepository.ivyLikePath(
|
ivyLikePath(
|
||||||
dependency.module.organization,
|
dependency.module.organization,
|
||||||
dependency.module.name,
|
dependency.module.name,
|
||||||
project.version,
|
project.version,
|
||||||
|
|
@ -120,12 +121,20 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
|
||||||
val path =
|
val path =
|
||||||
if (ivyLike)
|
if (ivyLike)
|
||||||
ivyLikePath0(dependency.attributes.`type` + "s", "", dependency.attributes.`type`)
|
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.organization.split('.').toSeq ++ Seq(
|
||||||
dependency.module.name,
|
dependency.module.name,
|
||||||
project.version,
|
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 =
|
var artifact =
|
||||||
Artifact(
|
Artifact(
|
||||||
|
|
@ -139,6 +148,9 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
|
||||||
if (dependency.attributes.`type` == "jar") {
|
if (dependency.attributes.`type` == "jar") {
|
||||||
artifact = artifact.withDefaultSignature
|
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 =
|
artifact =
|
||||||
if (ivyLike) {
|
if (ivyLike) {
|
||||||
val srcPath = root + ivyLikePath0("srcs", "-sources", "jar").mkString("/")
|
val srcPath = root + ivyLikePath0("srcs", "-sources", "jar").mkString("/")
|
||||||
|
|
@ -181,6 +193,17 @@ object BaseMavenRepository {
|
||||||
s"$name$baseSuffix.$ext"
|
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(
|
abstract class BaseMavenRepository(
|
||||||
|
|
@ -198,16 +221,27 @@ abstract class BaseMavenRepository(
|
||||||
|
|
||||||
val source = MavenSource(root, ivyLike)
|
val source = MavenSource(root, ivyLike)
|
||||||
|
|
||||||
def projectArtifact(module: Module, version: String): Artifact = {
|
def projectArtifact(
|
||||||
|
module: Module,
|
||||||
|
version: String,
|
||||||
|
versioningValue: Option[String]
|
||||||
|
): Artifact = {
|
||||||
|
|
||||||
val path = (
|
val path = (
|
||||||
if (ivyLike)
|
if (ivyLike)
|
||||||
ivyLikePath(module.organization, module.name, version, "poms", "", "pom")
|
ivyLikePath(
|
||||||
|
module.organization,
|
||||||
|
module.name,
|
||||||
|
versioningValue getOrElse version,
|
||||||
|
"poms",
|
||||||
|
"",
|
||||||
|
"pom"
|
||||||
|
)
|
||||||
else
|
else
|
||||||
module.organization.split('.').toSeq ++ Seq(
|
module.organization.split('.').toSeq ++ Seq(
|
||||||
module.name,
|
module.name,
|
||||||
version,
|
version,
|
||||||
s"${module.name}-$version.pom"
|
s"${module.name}-${versioningValue getOrElse version}.pom"
|
||||||
)
|
)
|
||||||
) .map(encodeURIComponent)
|
) .map(encodeURIComponent)
|
||||||
|
|
||||||
|
|
@ -242,6 +276,32 @@ abstract class BaseMavenRepository(
|
||||||
Some(artifact)
|
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(
|
def versions(
|
||||||
module: Module,
|
module: Module,
|
||||||
cachePolicy: CachePolicy = CachePolicy.Default
|
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(
|
def findNoInterval(
|
||||||
module: Module,
|
module: Module,
|
||||||
version: String,
|
version: String,
|
||||||
cachePolicy: CachePolicy
|
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[Task, String, Project] = {
|
||||||
|
|
||||||
EitherT {
|
EitherT {
|
||||||
fetch(projectArtifact(module, version), cachePolicy)
|
fetch(projectArtifact(module, version, versioningValue), cachePolicy)
|
||||||
.run
|
.run
|
||||||
.map(eitherStr =>
|
.map(eitherStr =>
|
||||||
for {
|
for {
|
||||||
|
|
|
||||||
|
|
@ -210,10 +210,24 @@ object Xml {
|
||||||
depMgmts,
|
depMgmts,
|
||||||
properties.toMap,
|
properties.toMap,
|
||||||
profiles,
|
profiles,
|
||||||
|
None,
|
||||||
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 = {
|
def versions(node: Node): String \/ Versions = {
|
||||||
import Scalaz._
|
import Scalaz._
|
||||||
|
|
||||||
|
|
@ -230,24 +244,111 @@ object Xml {
|
||||||
release = text(xmlVersioning, "release", "Release version")
|
release = text(xmlVersioning, "release", "Release version")
|
||||||
.getOrElse("")
|
.getOrElse("")
|
||||||
|
|
||||||
versions <- xmlVersioning.child
|
versionsOpt = xmlVersioning.child
|
||||||
.find(_.label == "versions")
|
.find(_.label == "versions")
|
||||||
.map(_.child.filter(_.label == "version").flatMap(_.child.collectFirst{case Text(t) => t}))
|
.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")
|
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
|
||||||
.toOption
|
.toOption
|
||||||
.filter(s => s.length == 14 && s.forall(_.isDigit))
|
.flatMap(parseDateTime)
|
||||||
.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
|
|
||||||
))
|
|
||||||
|
|
||||||
} 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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ package object test {
|
||||||
dependencyManagement: Seq[Dependency] = Seq.empty,
|
dependencyManagement: Seq[Dependency] = Seq.empty,
|
||||||
properties: Map[String, String] = Map.empty,
|
properties: Map[String, String] = Map.empty,
|
||||||
profiles: Seq[Profile] = Seq.empty,
|
profiles: Seq[Profile] = Seq.empty,
|
||||||
versions: Option[core.Versions] = None): Project =
|
versions: Option[core.Versions] = None,
|
||||||
core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles, versions)
|
snapshotVersioning: Option[core.SnapshotVersioning] = None): Project =
|
||||||
|
core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles, versions, snapshotVersioning)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue