From bb90017911d7b4ca7c3e35f94eb689e422ff7180 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 26 Mar 2015 16:09:08 -0400 Subject: [PATCH 1/3] Adding a test for #1921 --- .../dependency-management/transitive-version-range/build.sbt | 5 +++++ .../dependency-management/transitive-version-range/test | 1 + 2 files changed, 6 insertions(+) create mode 100644 sbt/src/sbt-test/dependency-management/transitive-version-range/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/transitive-version-range/test diff --git a/sbt/src/sbt-test/dependency-management/transitive-version-range/build.sbt b/sbt/src/sbt-test/dependency-management/transitive-version-range/build.sbt new file mode 100644 index 000000000..0f8c84c1e --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/transitive-version-range/build.sbt @@ -0,0 +1,5 @@ +organization := "com.test" +name := "maven-version-range-bug" +version := "1.0.0-SNAPSHOT" +scalaVersion := "2.11.6" +libraryDependencies += "com.amazonaws" % "aws-java-sdk" % "1.7.8.1" \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/transitive-version-range/test b/sbt/src/sbt-test/dependency-management/transitive-version-range/test new file mode 100644 index 000000000..edf1fbdd5 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/transitive-version-range/test @@ -0,0 +1 @@ +> update \ No newline at end of file From edeb9815ae250cfce227fa11ff6049278a420342 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 26 Mar 2015 16:09:29 -0400 Subject: [PATCH 2/3] Fixes #1921. * Add version range query (if needed) before resolving artifact in Aether. * Ensure detected version is used after having a version range query. --- ivy/src/main/scala/sbt/MakePom.scala | 96 ++++++++++--------- .../MavenCacheRepositoryResolver.scala | 6 +- .../MavenRemoteRepositoryResolver.scala | 9 +- .../mavenint/MavenRepositoryResolver.scala | 55 ++++++++--- .../MavenRepositorySystemFactory.scala | 4 +- 5 files changed, 111 insertions(+), 59 deletions(-) diff --git a/ivy/src/main/scala/sbt/MakePom.scala b/ivy/src/main/scala/sbt/MakePom.scala index 072a1a055..d0e1a2741 100644 --- a/ivy/src/main/scala/sbt/MakePom.scala +++ b/ivy/src/main/scala/sbt/MakePom.scala @@ -21,8 +21,61 @@ import org.apache.ivy.core.settings.IvySettings import org.apache.ivy.core.module.descriptor.{ DependencyArtifactDescriptor, DependencyDescriptor, License, ModuleDescriptor, ExcludeRule } import org.apache.ivy.plugins.resolver.{ ChainResolver, DependencyResolver, IBiblioResolver } import ivyint.CustomRemoteMavenResolver +object MakePom { + /** True if the revision is an ivy-range, not a complete revision. */ + def isDependencyVersionRange(revision: String): Boolean = { + (revision endsWith "+") || + (revision contains "[") || + (revision contains "]") || + (revision contains "(") || + (revision contains ")") + } + /** Converts Ivy revision ranges to that of Maven POM */ + def makeDependencyVersion(revision: String): String = { + def plusRange(s: String, shift: Int = 0) = { + def pow(i: Int): Int = if (i > 0) 10 * pow(i - 1) else 1 + val (prefixVersion, lastVersion) = (s + "0" * shift).reverse.split("\\.", 2) match { + case Array(revLast, revRest) => + (revRest.reverse + ".", revLast.reverse) + case Array(revLast) => ("", revLast.reverse) + } + val lastVersionInt = lastVersion.toInt + s"[${prefixVersion}${lastVersion},${prefixVersion}${lastVersionInt + pow(shift)})" + } + val startSym = Set(']', '[', '(') + val stopSym = Set(']', '[', ')') + val DotPlusPattern = """(.+)\.\+""".r + val DotNumPlusPattern = """(.+)\.(\d+)\+""".r + val NumPlusPattern = """(\d+)\+""".r + val maxDigit = 5 + try { + revision match { + case "+" => "[0,)" + case DotPlusPattern(base) => plusRange(base) + // This is a heuristic. Maven just doesn't support Ivy's notions of 1+, so + // we assume version ranges never go beyond 5 siginificant digits. + case NumPlusPattern(tail) => (0 until maxDigit).map(plusRange(tail, _)).mkString(",") + case DotNumPlusPattern(base, tail) => (0 until maxDigit).map(plusRange(base + "." + tail, _)).mkString(",") + case rev if rev endsWith "+" => sys.error(s"dynamic revision '$rev' cannot be translated to POM") + case rev if startSym(rev(0)) && stopSym(rev(rev.length - 1)) => + val start = rev(0) + val stop = rev(rev.length - 1) + val mid = rev.substring(1, rev.length - 1) + (if (start == ']') "(" else start) + mid + (if (stop == '[') ")" else stop) + case _ => revision + } + } catch { + case e: NumberFormatException => + // TODO - if the version doesn't meet our expectations, maybe we just issue a hard + // error instead of softly ignoring the attempt to rewrite. + //sys.error(s"Could not fix version [$revision] into maven style version") + revision + } + } +} class MakePom(val log: Logger) { + import MakePom._ @deprecated("Use `write(Ivy, ModuleDescriptor, ModuleInfo, Option[Iterable[Configuration]], Set[String], NodeSeq, XNode => XNode, MavenRepository => Boolean, Boolean, File)` instead", "0.11.2") def write(ivy: Ivy, module: ModuleDescriptor, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], extra: NodeSeq, process: XNode => XNode, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean, output: File): Unit = write(ivy, module, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], Set(Artifact.DefaultType), extra, process, filterRepositories, allRepositories, output) @@ -218,49 +271,6 @@ class MakePom(val log: Logger) { } - /** Converts Ivy revision ranges to that of Maven POM */ - def makeDependencyVersion(revision: String): String = { - def plusRange(s: String, shift: Int = 0) = { - def pow(i: Int): Int = if (i > 0) 10 * pow(i - 1) else 1 - val (prefixVersion, lastVersion) = (s + "0" * shift).reverse.split("\\.", 2) match { - case Array(revLast, revRest) => - (revRest.reverse + ".", revLast.reverse) - case Array(revLast) => ("", revLast.reverse) - } - val lastVersionInt = lastVersion.toInt - s"[${prefixVersion}${lastVersion},${prefixVersion}${lastVersionInt + pow(shift)})" - } - val startSym = Set(']', '[', '(') - val stopSym = Set(']', '[', ')') - val DotPlusPattern = """(.+)\.\+""".r - val DotNumPlusPattern = """(.+)\.(\d+)\+""".r - val NumPlusPattern = """(\d+)\+""".r - val maxDigit = 5 - try { - revision match { - case "+" => "[0,)" - case DotPlusPattern(base) => plusRange(base) - // This is a heuristic. Maven just doesn't support Ivy's notions of 1+, so - // we assume version ranges never go beyond 5 siginificant digits. - case NumPlusPattern(tail) => (0 until maxDigit).map(plusRange(tail, _)).mkString(",") - case DotNumPlusPattern(base, tail) => (0 until maxDigit).map(plusRange(base + "." + tail, _)).mkString(",") - case rev if rev endsWith "+" => sys.error(s"dynamic revision '$rev' cannot be translated to POM") - case rev if startSym(rev(0)) && stopSym(rev(rev.length - 1)) => - val start = rev(0) - val stop = rev(rev.length - 1) - val mid = rev.substring(1, rev.length - 1) - (if (start == ']') "(" else start) + mid + (if (stop == '[') ")" else stop) - case _ => revision - } - } catch { - case e: NumberFormatException => - // TODO - if the version doesn't meet our expectations, maybe we just issue a hard - // error instead of softly ignoring the attempt to rewrite. - //sys.error(s"Could not fix version [$revision] into maven style version") - revision - } - } - @deprecated("No longer used and will be removed.", "0.12.1") def classifier(dependency: DependencyDescriptor, includeTypes: Set[String]): NodeSeq = { diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenCacheRepositoryResolver.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenCacheRepositoryResolver.scala index de8c6d7a3..b996f9172 100644 --- a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenCacheRepositoryResolver.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenCacheRepositoryResolver.scala @@ -9,7 +9,9 @@ import org.eclipse.aether.metadata.{ DefaultMetadata, Metadata } import org.eclipse.aether.resolution.{ ArtifactDescriptorRequest => AetherDescriptorRequest, ArtifactRequest => AetherArtifactRequest, - MetadataRequest => AetherMetadataRequest + MetadataRequest => AetherMetadataRequest, + VersionRequest => AetherVersionRequest, + VersionRangeRequest => AetherVersionRangeRequest } import sbt.ivyint.CustomMavenResolver @@ -29,6 +31,8 @@ class MavenCacheRepositoryResolver(val repo: MavenCache, settings: IvySettings) protected def setRepository(request: AetherMetadataRequest): AetherMetadataRequest = request protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest = request protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest = request + protected def addRepositories(request: AetherVersionRequest): AetherVersionRequest = request + protected def addRepositories(request: AetherVersionRangeRequest): AetherVersionRangeRequest = request protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = { val request = new AetherInstallRequest() artifacts foreach request.addArtifact diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRemoteRepositoryResolver.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRemoteRepositoryResolver.scala index 1661832b2..067c2fa11 100644 --- a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRemoteRepositoryResolver.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRemoteRepositoryResolver.scala @@ -11,7 +11,9 @@ import org.eclipse.aether.resolution.{ ArtifactDescriptorRequest => AetherDescriptorRequest, ArtifactDescriptorResult => AetherDescriptorResult, ArtifactRequest => AetherArtifactRequest, - MetadataRequest => AetherMetadataRequest + MetadataRequest => AetherMetadataRequest, + VersionRequest => AetherVersionRequest, + VersionRangeRequest => AetherVersionRangeRequest } import sbt.ivyint.CustomRemoteMavenResolver import scala.collection.JavaConverters._ @@ -43,6 +45,11 @@ class MavenRemoteRepositoryResolver(val repo: MavenRepository, settings: IvySett if (isUseCacheOnly) request else request.addRepository(aetherRepository) protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest = if (isUseCacheOnly) request else request.addRepository(aetherRepository) + protected def addRepositories(request: AetherVersionRequest): AetherVersionRequest = + if (isUseCacheOnly) request else request.addRepository(aetherRepository) + protected def addRepositories(request: AetherVersionRangeRequest): AetherVersionRangeRequest = + if (isUseCacheOnly) request else request.addRepository(aetherRepository) + /** Actually publishes aether artifacts. */ protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = { val request = new AetherDeployRequest() diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala index b39bfb87a..6796efcd7 100644 --- a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala @@ -20,7 +20,15 @@ import org.eclipse.aether.artifact.{ DefaultArtifact => AetherArtifact } import org.eclipse.aether.deployment.{ DeployRequest => AetherDeployRequest } import org.eclipse.aether.installation.{ InstallRequest => AetherInstallRequest } import org.eclipse.aether.metadata.{ DefaultMetadata, Metadata } -import org.eclipse.aether.resolution.{ ArtifactDescriptorRequest => AetherDescriptorRequest, ArtifactDescriptorResult => AetherDescriptorResult, ArtifactRequest => AetherArtifactRequest, ArtifactResolutionException, MetadataRequest => AetherMetadataRequest } +import org.eclipse.aether.resolution.{ + ArtifactDescriptorRequest => AetherDescriptorRequest, + ArtifactDescriptorResult => AetherDescriptorResult, + ArtifactRequest => AetherArtifactRequest, + ArtifactResolutionException, + MetadataRequest => AetherMetadataRequest, + VersionRequest => AetherVersionRequest, + VersionRangeRequest => AetherVersionRangeRequest +} import org.eclipse.aether.{ RepositorySystem, RepositorySystemSession } import sbt.ivyint.{ CustomMavenResolver, CustomRemoteMavenResolver } import sbt.mavenint.MavenRepositoryResolver.JarPackaging @@ -67,6 +75,8 @@ abstract class MavenRepositoryResolver(settings: IvySettings) extends AbstractRe /** Inject necessary repositories into a descriptor request. */ protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest + protected def addRepositories(request: AetherVersionRequest): AetherVersionRequest + protected def addRepositories(request: AetherVersionRangeRequest): AetherVersionRangeRequest /** Actually publishes aether artifacts. */ protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit @@ -105,29 +115,49 @@ abstract class MavenRepositoryResolver(settings: IvySettings) extends AbstractRe override def getDependency(dd: DependencyDescriptor, rd: ResolveData): ResolvedModuleRevision = { val context = IvyContext.pushNewCopyContext try { + val drid: ModuleRevisionId = + if (sbt.MakePom.isDependencyVersionRange(dd.getDependencyRevisionId.getRevision)) { + Message.debug(s"Got a dynamic revision, attempting to convert to real revision: ${dd.getDependencyRevisionId}") + val revision = sbt.MakePom.makeDependencyVersion(dd.getDependencyRevisionId.getRevision) + // TODO - Alter revision id to be maven-friendly first. + val coords = + s"${dd.getDependencyRevisionId.getOrganisation}:${aetherArtifactIdFromMrid(dd.getDependencyRevisionId)}:${revision}" + //val coords = aetherCoordsFromMrid(dd.getDependencyRevisionId) + Message.debug(s"Aether about to resolve version for [$coords]...") + val versionRequest = addRepositories(new AetherVersionRangeRequest().setArtifact(new AetherArtifact(coords, getArtifactProperties(dd.getDependencyRevisionId)))) + val result = system.resolveVersionRange(session, versionRequest) + Message.debug(s"Version result = $result, from $getName") + if (result.getVersions.isEmpty) throw new MavenResolutionException(s"Did not find any versions for $dd") + ModuleRevisionId.newInstance( + dd.getDependencyRevisionId.getOrganisation, + dd.getDependencyRevisionId.getName, + result.getHighestVersion.toString, + dd.getExtraAttributes) + } else dd.getDependencyRevisionId + // TODO - Check to see if we're asking for latest.* version, and if so, we should run a latest version query // first and use that result to return the metadata/final module. - Message.debug(s"Requesting conf [${dd.getModuleConfigurations.mkString(",")}] from Aether module ${dd.getDependencyRevisionId} in resolver ${getName}") + Message.debug(s"Requesting conf [${dd.getModuleConfigurations.mkString(",")}] from Aether module ${drid} in resolver ${getName}") val request = new AetherDescriptorRequest() - val coords = aetherCoordsFromMrid(dd.getDependencyRevisionId) + val coords = aetherCoordsFromMrid(drid) Message.debug(s"Aether about to resolve [$coords]...") - request.setArtifact(new AetherArtifact(coords, getArtifactProperties(dd.getDependencyRevisionId))) + request.setArtifact(new AetherArtifact(coords, getArtifactProperties(drid))) addRepositories(request) val result = system.readArtifactDescriptor(session, request) val packaging = getPackagingFromPomProperties(result.getProperties) Message.debug(s"Aether resolved ${dd.getDependencyId} w/ packaging ${packaging}") // TODO - better pub date if we have no metadata. - val lastModifiedTime = getPublicationTime(dd.getDependencyRevisionId) getOrElse 0L + val lastModifiedTime = getPublicationTime(drid) getOrElse 0L // Construct a new Ivy module descriptor val desc: ModuleDescriptor = { // TODO - Better detection of snapshot and handling latest.integration/latest.snapshot val status = - if (dd.getDependencyRevisionId.getRevision.endsWith("-SNAPSHOT")) "integration" + if (drid.getRevision.endsWith("-SNAPSHOT")) "integration" else "release" val md = - new DefaultModuleDescriptor(dd.getDependencyRevisionId, status, null /* pubDate */ , false) + new DefaultModuleDescriptor(drid, status, null /* pubDate */ , false) //DefaultModuleDescriptor.newDefaultInstance(dd.getDependencyRevisionId) // Here we add the standard configurations for (config <- PomModuleDescriptorBuilder.MAVEN2_CONFIGURATIONS) { @@ -136,7 +166,8 @@ abstract class MavenRepositoryResolver(settings: IvySettings) extends AbstractRe // Here we look into the artifacts specified from the dependency descriptor *and* those that are defaulted, // and append them to the appropriate configurations. - addArtifactsFromPom(dd, packaging, md, lastModifiedTime) + // TODO - Feed correct revision down here + addArtifactsFromPom(drid, dd, packaging, md, lastModifiedTime) // Here we add dependencies. addDependenciesFromAether(result, md) // Here we use pom.xml Dependency management section to create Ivy dependency mediators. @@ -157,7 +188,7 @@ abstract class MavenRepositoryResolver(settings: IvySettings) extends AbstractRe } // Here we need to pretend we downloaded the pom.xml file - val pom = DefaultArtifact.newPomArtifact(dd.getDependencyRevisionId, new java.util.Date(lastModifiedTime)) + val pom = DefaultArtifact.newPomArtifact(drid, new java.util.Date(lastModifiedTime)) val madr = new MetadataArtifactDownloadReport(pom) madr.setSearched(true) madr.setDownloadStatus(DownloadStatus.SUCCESSFUL) // TODO - Figure this things out for this report. @@ -223,13 +254,13 @@ abstract class MavenRepositoryResolver(settings: IvySettings) extends AbstractRe } /** Determines which artifacts are associated with this maven module and appends them to the descriptor. */ - def addArtifactsFromPom(dd: DependencyDescriptor, packaging: String, md: DefaultModuleDescriptor, lastModifiedTime: Long): Unit = { + def addArtifactsFromPom(drid: ModuleRevisionId, dd: DependencyDescriptor, packaging: String, md: DefaultModuleDescriptor, lastModifiedTime: Long): Unit = { Message.debug(s"Calculating artifacts for ${dd.getDependencyId} w/ packaging $packaging") // Here we add in additional artifact requests, which ALLWAYS have to be explicit since // Maven/Aether doesn't include all known artifacts in a pom.xml // TODO - This does not appear to be working correctly. - if (dd.getAllDependencyArtifacts.isEmpty) { - val artifactId = s"${dd.getDependencyId.getName}-${dd.getDependencyRevisionId.getRevision}" + if (dd.getAllDependencyArtifacts.isEmpty) { + val artifactId = s"${drid.getName}-${drid.getRevision}" // Add the artifacts we know about the module packaging match { case "pom" => diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositorySystemFactory.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositorySystemFactory.scala index 016aa841a..61ec668de 100644 --- a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositorySystemFactory.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositorySystemFactory.scala @@ -6,10 +6,10 @@ import org.apache.ivy.plugins.repository.Resource import org.apache.ivy.plugins.repository.url.URLResource import org.apache.ivy.util.Message import org.apache.ivy.util.url.URLHandlerRegistry -import org.apache.maven.repository.internal.{ MavenRepositorySystemUtils, SbtArtifactDescriptorReader, SnapshotMetadataGeneratorFactory, VersionsMetadataGeneratorFactory } +import org.apache.maven.repository.internal.{ MavenRepositorySystemUtils, SbtArtifactDescriptorReader, SnapshotMetadataGeneratorFactory, VersionsMetadataGeneratorFactory, DefaultVersionResolver } import org.eclipse.aether.{ RepositorySystem, RepositorySystemSession } import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory -import org.eclipse.aether.impl.{ ArtifactDescriptorReader, DefaultServiceLocator, MetadataGeneratorFactory } +import org.eclipse.aether.impl.{ ArtifactDescriptorReader, DefaultServiceLocator, MetadataGeneratorFactory, VersionResolver } import org.eclipse.aether.repository.{ LocalRepository, RemoteRepository } import org.eclipse.aether.spi.connector.RepositoryConnectorFactory import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory From 3611ba4943c883f9fbca110df909dd93fe830bc4 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 27 Mar 2015 07:51:37 -0400 Subject: [PATCH 3/3] Fix unit test for makepom. --- ivy/src/test/scala/MakePomSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ivy/src/test/scala/MakePomSpec.scala b/ivy/src/test/scala/MakePomSpec.scala index 3310ed62b..68ede8bc8 100644 --- a/ivy/src/test/scala/MakePomSpec.scala +++ b/ivy/src/test/scala/MakePomSpec.scala @@ -61,10 +61,10 @@ class MakePomSpec extends Specification { val mp = new MakePom(ConsoleLogger()) def convertTo(s: String, expected: String) = - mp.makeDependencyVersion(s) must_== expected + MakePom.makeDependencyVersion(s) must_== expected def beParsedAsError(s: String) = try { - mp.makeDependencyVersion(s) + MakePom.makeDependencyVersion(s) failure } catch { case e: Throwable => success