diff --git a/ivy/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositoryResolver.scala b/ivy/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositoryResolver.scala index 84411bf52..6ff019c6a 100644 --- a/ivy/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositoryResolver.scala +++ b/ivy/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositoryResolver.scala @@ -29,7 +29,7 @@ import org.eclipse.aether.resolution.{ import org.eclipse.aether.deployment.{ DeployRequest => AetherDeployRequest } import org.eclipse.aether.installation.{ InstallRequest => AetherInstallRequest } import org.apache.ivy.core.cache.{ ModuleDescriptorWriter, ArtifactOrigin } -import sbt.{ CustomPomParser, MavenCache, MavenRepository } +import sbt.{ MavenCache, MavenRepository } import scala.collection.JavaConverters._ object MavenRepositoryResolver { @@ -336,11 +336,11 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab } def getSbtVersion(dd: ModuleRevisionId): Option[String] = - Option(dd.getExtraAttribute(CustomPomParser.SbtVersionKey)) + Option(dd.getExtraAttribute(PomExtraDependencyAttributes.SbtVersionKey)) def getArtifactProperties(dd: ModuleRevisionId): java.util.Map[String, String] = { val m = new java.util.HashMap[String, String] - Option(dd.getExtraAttribute(CustomPomParser.ScalaVersionKey)) foreach { sv => + Option(dd.getExtraAttribute(PomExtraDependencyAttributes.ScalaVersionKey)) foreach { sv => m.put(SbtExtraProperties.POM_SCALA_VERSION, sv) } getSbtVersion(dd) foreach { sv => diff --git a/ivy/src/main/scala/org/apache/maven/repository/internal/PomExtraDependencyAttributes.scala b/ivy/src/main/scala/org/apache/maven/repository/internal/PomExtraDependencyAttributes.scala index 573791420..4c2ead5d1 100644 --- a/ivy/src/main/scala/org/apache/maven/repository/internal/PomExtraDependencyAttributes.scala +++ b/ivy/src/main/scala/org/apache/maven/repository/internal/PomExtraDependencyAttributes.scala @@ -1,16 +1,29 @@ package org.apache.maven.repository.internal import java.util.Properties +import java.util.regex.Pattern +import org.apache.ivy.core.module.descriptor.DependencyDescriptor import org.apache.ivy.core.module.id.ModuleRevisionId import org.apache.ivy.util.extendable.ExtendableItem -import sbt.CustomPomParser +/** + * This class contains all the logic for dealing with the extra attributes in pom files relating to extra attributes + * on dependency declarations. + * + * Specifically, if we have a dependency on an sbt plugin, there are two properties that need to propogate: + * - `sbtVersion` + * - `scalaVersion` + * + * These need to exist on the *dependency declaration*. Maven/Aether has no way to inject these into + * the section of pom files, so we use Ivy's Extra attribute hackery to inject a lookup table + * of extra attributes by dependency id into POM files and later we read these back. + */ object PomExtraDependencyAttributes { - // TODO - Move custom pom parser extra attribtues code into this class, rather than relying on - // custom pom parser (so we can deprecate it completely). - import CustomPomParser.{ ExtraAttributesKey, simplify, filterCustomExtra, readDependencyExtra } + val ExtraAttributesKey = "extraDependencyAttributes" + val SbtVersionKey = "sbtVersion" + val ScalaVersionKey = "scalaVersion" /** * Reads the extra dependency attributes out of a maven property. * @param props The properties from an Aether resolution. @@ -44,4 +57,55 @@ object PomExtraDependencyAttributes { case None => } } + + /** + * Reads the extra dependency information out of Ivy's notion of POM properties and returns + * the map of ID -> Extra Properties. + */ + def getDependencyExtra(m: Map[String, String]): Map[ModuleRevisionId, Map[String, String]] = + (m get ExtraAttributesKey) match { + case None => Map.empty + case Some(str) => + def processDep(m: ModuleRevisionId) = (simplify(m), filterCustomExtra(m, include = true)) + readDependencyExtra(str).map(processDep).toMap + } + + def qualifiedExtra(item: ExtendableItem): Map[String, String] = { + import collection.JavaConverters._ + item.getQualifiedExtraAttributes.asInstanceOf[java.util.Map[String, String]].asScala.toMap + } + def filterCustomExtra(item: ExtendableItem, include: Boolean): Map[String, String] = + (qualifiedExtra(item) filterKeys { k => qualifiedIsExtra(k) == include }) + + def qualifiedIsExtra(k: String): Boolean = k.endsWith(ScalaVersionKey) || k.endsWith(SbtVersionKey) + + // Reduces the id to exclude custom extra attributes + // This makes the id suitable as a key to associate a dependency parsed from a element + // with the extra attributes from the section + def simplify(id: ModuleRevisionId): ModuleRevisionId = { + import collection.JavaConverters._ + ModuleRevisionId.newInstance(id.getOrganisation, id.getName, id.getBranch, id.getRevision, filterCustomExtra(id, include = false).asJava) + } + + /** parses the sequence of dependencies with extra attribute information, with one dependency per line */ + def readDependencyExtra(s: String): Seq[ModuleRevisionId] = + LinesP.split(s).map(_.trim).filter(!_.isEmpty).map(ModuleRevisionId.decode) + + private[this] val LinesP = Pattern.compile("(?m)^") + + /** + * Creates the "extra" property values for DependencyDescriptors that can be written into a maven pom + * so we don't loose the information. + * @param s + * @return + */ + def writeDependencyExtra(s: Seq[DependencyDescriptor]): Seq[String] = + s.flatMap { dd => + val revId = dd.getDependencyRevisionId + if (filterCustomExtra(revId, include = true).isEmpty) + Nil + else + revId.encodeToString :: Nil + } + } diff --git a/ivy/src/main/scala/org/apache/maven/repository/internal/SbtExtraProperties.java b/ivy/src/main/scala/org/apache/maven/repository/internal/SbtExtraProperties.java index 55385f9f0..5570d4fac 100644 --- a/ivy/src/main/scala/org/apache/maven/repository/internal/SbtExtraProperties.java +++ b/ivy/src/main/scala/org/apache/maven/repository/internal/SbtExtraProperties.java @@ -3,15 +3,18 @@ package org.apache.maven.repository.internal; /** - * Created by jsuereth on 12/20/14. + * Extra properties we dump from Aether into the properties list. */ public class SbtExtraProperties { + public static final String MAVEN_PACKAGING_KEY = "sbt.pom.packaging"; public static final String SCALA_VERSION_KEY = "sbt.pom.scalaversion"; public static final String SBT_VERSION_KEY = "sbt.pom.sbtversion"; + public static final String POM_INFO_KEY_PREFIX = "info."; public static final String POM_SCALA_VERSION = "scalaVersion"; public static final String POM_SBT_VERSION = "sbtVersion"; + public static final String POM_API_KEY = "info.apiURL"; public static final String LICENSE_COUNT_KEY = "license.count"; diff --git a/ivy/src/main/scala/sbt/CustomPomParser.scala b/ivy/src/main/scala/sbt/CustomPomParser.scala index d639532a7..d931cefe6 100644 --- a/ivy/src/main/scala/sbt/CustomPomParser.scala +++ b/ivy/src/main/scala/sbt/CustomPomParser.scala @@ -13,6 +13,8 @@ import java.io.{ File, InputStream } import java.net.URL import java.util.regex.Pattern +import org.apache.maven.repository.internal.{ SbtExtraProperties, PomExtraDependencyAttributes } + @deprecated("0.13.8", "We now use an Aether-based pom parser.") final class CustomPomParser(delegate: ModuleDescriptorParser, transform: (ModuleDescriptorParser, ModuleDescriptor) => ModuleDescriptor) extends ModuleDescriptorParser { override def parseDescriptor(ivySettings: ParserSettings, descriptorURL: URL, validate: Boolean) = @@ -34,15 +36,16 @@ object CustomPomParser { ReplaceMavenConfigurationMappings.init() /** The key prefix that indicates that this is used only to store extra information and is not intended for dependency resolution.*/ - val InfoKeyPrefix = "info." - val ApiURLKey = "info.apiURL" + val InfoKeyPrefix = SbtExtraProperties.POM_INFO_KEY_PREFIX + val ApiURLKey = SbtExtraProperties.POM_API_KEY - val SbtVersionKey = "sbtVersion" - val ScalaVersionKey = "scalaVersion" - val ExtraAttributesKey = "extraDependencyAttributes" + val SbtVersionKey = PomExtraDependencyAttributes.SbtVersionKey + val ScalaVersionKey = PomExtraDependencyAttributes.ScalaVersionKey + val ExtraAttributesKey = PomExtraDependencyAttributes.ExtraAttributesKey private[this] val unqualifiedKeys = Set(SbtVersionKey, ScalaVersionKey, ExtraAttributesKey, ApiURLKey) // packagings that should be jars, but that Ivy doesn't handle as jars + // TODO - move this elsewhere. val JarPackagings = Set("eclipse-plugin", "hk2-jar", "orbit", "scala-jar") val default = new CustomPomParser(PomModuleDescriptorParser.getInstance, defaultTransform) @@ -125,46 +128,24 @@ object CustomPomParser { } private[this] def getDependencyExtra(m: Map[String, String]): Map[ModuleRevisionId, Map[String, String]] = - (m get ExtraAttributesKey) match { - case None => Map.empty - case Some(str) => - def processDep(m: ModuleRevisionId) = (simplify(m), filterCustomExtra(m, include = true)) - readDependencyExtra(str).map(processDep).toMap - } + PomExtraDependencyAttributes.getDependencyExtra(m) - def qualifiedExtra(item: ExtendableItem): Map[String, String] = - { - import collection.JavaConverters._ - item.getQualifiedExtraAttributes.asInstanceOf[java.util.Map[String, String]].asScala.toMap - } + def qualifiedExtra(item: ExtendableItem): Map[String, String] = PomExtraDependencyAttributes.qualifiedExtra(item) def filterCustomExtra(item: ExtendableItem, include: Boolean): Map[String, String] = (qualifiedExtra(item) filterKeys { k => qualifiedIsExtra(k) == include }) def writeDependencyExtra(s: Seq[DependencyDescriptor]): Seq[String] = - s.flatMap { dd => - val revId = dd.getDependencyRevisionId - if (filterCustomExtra(revId, include = true).isEmpty) - Nil - else - revId.encodeToString :: Nil - } + PomExtraDependencyAttributes.writeDependencyExtra(s) // parses the sequence of dependencies with extra attribute information, with one dependency per line - def readDependencyExtra(s: String): Seq[ModuleRevisionId] = - LinesP.split(s).map(_.trim).filter(!_.isEmpty).map(ModuleRevisionId.decode) + def readDependencyExtra(s: String): Seq[ModuleRevisionId] = PomExtraDependencyAttributes.readDependencyExtra(s) - private[this] val LinesP = Pattern.compile("(?m)^") - - def qualifiedIsExtra(k: String): Boolean = k.endsWith(ScalaVersionKey) || k.endsWith(SbtVersionKey) + def qualifiedIsExtra(k: String): Boolean = PomExtraDependencyAttributes.qualifiedIsExtra(k) // Reduces the id to exclude custom extra attributes // This makes the id suitable as a key to associate a dependency parsed from a element // with the extra attributes from the section - def simplify(id: ModuleRevisionId): ModuleRevisionId = - { - import collection.JavaConverters._ - ModuleRevisionId.newInstance(id.getOrganisation, id.getName, id.getBranch, id.getRevision, filterCustomExtra(id, include = false).asJava) - } + def simplify(id: ModuleRevisionId): ModuleRevisionId = PomExtraDependencyAttributes.simplify(id) private[this] def addExtra(dep: DependencyDescriptor, extra: Map[ModuleRevisionId, Map[String, String]]): DependencyDescriptor = { diff --git a/ivy/src/main/scala/sbt/MakePom.scala b/ivy/src/main/scala/sbt/MakePom.scala index 3bc3520fd..13ca70f1b 100644 --- a/ivy/src/main/scala/sbt/MakePom.scala +++ b/ivy/src/main/scala/sbt/MakePom.scala @@ -8,6 +8,9 @@ package sbt import java.io.File + +import org.apache.maven.repository.internal.PomExtraDependencyAttributes + // Node needs to be renamed to XNode because the task subproject contains a Node type that will shadow // scala.xml.Node when generating aggregated API documentation import scala.xml.{ Elem, Node => XNode, NodeSeq, PrettyPrinter, PrefixedAttribute } @@ -119,12 +122,12 @@ class MakePom(val log: Logger) { def makeProperties(module: ModuleDescriptor, dependencies: Seq[DependencyDescriptor]): NodeSeq = { val extra = IvySbt.getExtraAttributes(module) - val depExtra = CustomPomParser.writeDependencyExtra(dependencies).mkString("\n") - val allExtra = if (depExtra.isEmpty) extra else extra.updated(CustomPomParser.ExtraAttributesKey, depExtra) + val depExtra = PomExtraDependencyAttributes.writeDependencyExtra(dependencies).mkString("\n") + val allExtra = if (depExtra.isEmpty) extra else extra.updated(PomExtraDependencyAttributes.ExtraAttributesKey, depExtra) if (allExtra.isEmpty) NodeSeq.Empty else makeProperties(allExtra) } def makeProperties(extra: Map[String, String]): NodeSeq = { - def _extraAttributes(k: String) = if (k == CustomPomParser.ExtraAttributesKey) xmlSpacePreserve else scala.xml.Null + def _extraAttributes(k: String) = if (k == PomExtraDependencyAttributes.ExtraAttributesKey) xmlSpacePreserve else scala.xml.Null { for ((key, value) <- extra) yield ({ value }).copy(label = key, attributes = _extraAttributes(key)) } diff --git a/ivy/src/main/scala/sbt/ModuleID.scala b/ivy/src/main/scala/sbt/ModuleID.scala index 178940666..bf734f5f7 100644 --- a/ivy/src/main/scala/sbt/ModuleID.scala +++ b/ivy/src/main/scala/sbt/ModuleID.scala @@ -5,6 +5,8 @@ package sbt import java.net.URL +import org.apache.maven.repository.internal.SbtExtraProperties + final case class ModuleID(organization: String, name: String, revision: String, configurations: Option[String] = None, isChanging: Boolean = false, isTransitive: Boolean = true, isForce: Boolean = false, explicitArtifacts: Seq[Artifact] = Nil, exclusions: Seq[ExclusionRule] = Nil, extraAttributes: Map[String, String] = Map.empty, crossVersion: CrossVersion = CrossVersion.Disabled) { override def toString: String = organization + ":" + name + ":" + revision + @@ -15,7 +17,7 @@ final case class ModuleID(organization: String, name: String, revision: String, def extraString: String = extraDependencyAttributes.map { case (k, v) => k + "=" + v } mkString ("(", ", ", ")") /** Returns the extra attributes except for ones marked as information only (ones that typically would not be used for dependency resolution). */ - def extraDependencyAttributes: Map[String, String] = extraAttributes.filterKeys(!_.startsWith(CustomPomParser.InfoKeyPrefix)) + def extraDependencyAttributes: Map[String, String] = extraAttributes.filterKeys(!_.startsWith(SbtExtraProperties.POM_INFO_KEY_PREFIX)) @deprecated("Use `cross(CrossVersion)`, the variant accepting a CrossVersion value constructed by a member of the CrossVersion object instead.", "0.12.0") def cross(v: Boolean): ModuleID = cross(if (v) CrossVersion.binary else CrossVersion.Disabled) diff --git a/ivy/src/test/scala/MavenResolutionSpec.scala b/ivy/src/test/scala/MavenResolutionSpec.scala index f7b46895d..8015e78a8 100644 --- a/ivy/src/test/scala/MavenResolutionSpec.scala +++ b/ivy/src/test/scala/MavenResolutionSpec.scala @@ -2,6 +2,7 @@ package sbt import java.io.FileInputStream +import org.apache.maven.repository.internal.PomExtraDependencyAttributes import org.specs2._ class MavenResolutionSpec extends BaseIvySpecification { @@ -37,11 +38,11 @@ class MavenResolutionSpec extends BaseIvySpecification { def scalaContinuationPlugin = ModuleID("org.scala-lang.plugins", "continuations", "2.8.1", Some("plugin->default(compile)")) def sbtPlugin = ModuleID("com.github.mpeltonen", "sbt-idea", "1.6.0", Some("compile")). - extra(CustomPomParser.SbtVersionKey -> "0.13", CustomPomParser.ScalaVersionKey -> "2.10"). + extra(PomExtraDependencyAttributes.SbtVersionKey -> "0.13", PomExtraDependencyAttributes.ScalaVersionKey -> "2.10"). copy(crossVersion = CrossVersion.Disabled) def oldSbtPlugin = ModuleID("com.github.mpeltonen", "sbt-idea", "1.6.0", Some("compile")). - extra(CustomPomParser.SbtVersionKey -> "0.12", CustomPomParser.ScalaVersionKey -> "2.9.2"). + extra(PomExtraDependencyAttributes.SbtVersionKey -> "0.12", PomExtraDependencyAttributes.ScalaVersionKey -> "2.9.2"). copy(crossVersion = CrossVersion.Disabled) def majorConflictLib = ModuleID("com.joestelmach", "natty", "0.3", Some("compile")) // TODO - This snapshot and resolver should be something we own/control so it doesn't disappear on us.