diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala
index 21c6cf6e2..400710b67 100644
--- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala
+++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala
@@ -140,6 +140,7 @@ final class IvySbt(val configuration: IvyConfiguration) {
/**
* Cleans cached resolution cache.
+ *
* @param md - module descriptor of the original Ivy graph.
*/
private[sbt] def cleanCachedResolutionCache(md: ModuleDescriptor, log: Logger): Unit =
@@ -621,6 +622,12 @@ private[sbt] object IvySbt {
dependencyDescriptor.addExcludeRule(conf, IvyScala.excludeRule(excls.organization, excls.name, excls.configurations, excls.artifact))
}
}
+ for (incls <- dependency.inclusions) {
+ for (conf <- dependencyDescriptor.getModuleConfigurations) {
+ dependencyDescriptor.addIncludeRule(conf, IvyScala.includeRule(incls.organization, incls.name, incls.configurations, incls.artifact))
+ }
+ }
+
dependencyDescriptor
}
def copyConfigurations(artifact: Artifact, addConfiguration: String => Unit): Unit =
diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala
index 6f131f049..b197b75e0 100644
--- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala
+++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala
@@ -29,21 +29,27 @@ final class PublishConfiguration(val ivyFile: Option[File], val resolverName: St
this(ivyFile, resolverName, artifacts, checksums, logging, false)
}
-final class UpdateConfiguration(val retrieve: Option[RetrieveConfiguration], val missingOk: Boolean, val logging: UpdateLogging.Value) {
+final class UpdateConfiguration(val retrieve: Option[RetrieveConfiguration], val missingOk: Boolean, val logging: UpdateLogging.Value, val artifactFilter: ArtifactTypeFilter) {
+ @deprecated("You should use the constructor that provides an artifactFilter", "1.0.x")
+ def this(retrieve: Option[RetrieveConfiguration], missingOk: Boolean, logging: UpdateLogging.Value) {
+ this(retrieve, missingOk, logging, ArtifactTypeFilter.forbid(Set("src", "doc"))) // allow everything but "src", "doc" by default
+ }
+
private[sbt] def copy(
retrieve: Option[RetrieveConfiguration] = this.retrieve,
missingOk: Boolean = this.missingOk,
- logging: UpdateLogging.Value = this.logging
+ logging: UpdateLogging.Value = this.logging,
+ artifactFilter: ArtifactTypeFilter = this.artifactFilter
): UpdateConfiguration =
- new UpdateConfiguration(retrieve, missingOk, logging)
+ new UpdateConfiguration(retrieve, missingOk, logging, artifactFilter)
}
final class RetrieveConfiguration(val retrieveDirectory: File, val outputPattern: String, val sync: Boolean, val configurationsToRetrieve: Option[Set[Configuration]]) {
def this(retrieveDirectory: File, outputPattern: String) = this(retrieveDirectory, outputPattern, false, None)
def this(retrieveDirectory: File, outputPattern: String, sync: Boolean) = this(retrieveDirectory, outputPattern, sync, None)
}
final case class MakePomConfiguration(file: File, moduleInfo: ModuleInfo, configurations: Option[Seq[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true, allRepositories: Boolean, includeTypes: Set[String] = Set(Artifact.DefaultType, Artifact.PomType))
-// exclude is a map on a restricted ModuleID
-final case class GetClassifiersConfiguration(module: GetClassifiersModule, exclude: Map[ModuleID, Set[String]], configuration: UpdateConfiguration, ivyScala: Option[IvyScala])
+/** @param exclude is a map from ModuleID to classifiers that were previously tried and failed, so should now be excluded */
+final case class GetClassifiersConfiguration(module: GetClassifiersModule, exclude: Map[ModuleID, Set[String]], configuration: UpdateConfiguration, ivyScala: Option[IvyScala], sourceArtifactTypes: Set[String], docArtifactTypes: Set[String])
final case class GetClassifiersModule(id: ModuleID, modules: Seq[ModuleID], configurations: Seq[Configuration], classifiers: Seq[String])
final class UnresolvedWarningConfiguration private[sbt] (
@@ -180,6 +186,7 @@ object IvyActions {
val resolveOptions = new ResolveOptions
val resolveId = ResolveOptions.getDefaultResolveId(md)
resolveOptions.setResolveId(resolveId)
+ resolveOptions.setArtifactFilter(configuration.artifactFilter)
resolveOptions.setLog(ivyLogLevel(configuration.logging))
x.customResolve(md, configuration.missingOk, logicalClock, resolveOptions, depDir getOrElse { sys.error("dependency base directory is not specified") }, log) match {
case Left(x) =>
@@ -194,7 +201,7 @@ object IvyActions {
case (ivy, md, default) =>
val iw = IvySbt.inconsistentDuplicateWarning(md)
iw foreach { log.warn(_) }
- val (report, err) = resolve(configuration.logging)(ivy, md, default)
+ val (report, err) = resolve(configuration.logging)(ivy, md, default, configuration.artifactFilter)
err match {
case Some(x) if !configuration.missingOk =>
Left(UnresolvedWarning(x, uwconfig))
@@ -243,7 +250,15 @@ object IvyActions {
def updateClassifiers(ivySbt: IvySbt, config: GetClassifiersConfiguration, log: Logger): UpdateReport =
updateClassifiers(ivySbt, config, UnresolvedWarningConfiguration(), LogicalClock.unknown, None, Vector(), log)
- // artifacts can be obtained from calling toSeq on UpdateReport
+ /**
+ * Creates explicit artifacts for each classifier in `config.module`, and then attempts to resolve them directly. This
+ * is for Maven compatibility, where these artifacts are not "published" in the POM, so they don't end up in the Ivy
+ * that sbt generates for them either.
+ * Artifacts can be obtained from calling toSeq on UpdateReport.
+ * In addition, retrieves specific Ivy artifacts if they have one of the requested `config.configuration.types`.
+ * @param config important to set `config.configuration.types` to only allow artifact types that can correspond to
+ * "classified" artifacts (sources and javadocs).
+ */
private[sbt] def updateClassifiers(ivySbt: IvySbt, config: GetClassifiersConfiguration,
uwconfig: UnresolvedWarningConfiguration, logicalClock: LogicalClock, depDir: Option[File],
artifacts: Vector[(String, ModuleID, Artifact, File)],
@@ -252,14 +267,28 @@ object IvyActions {
import config.{ configuration => c, module => mod, _ }
import mod.{ configurations => confs, _ }
assert(classifiers.nonEmpty, "classifiers cannot be empty")
+ assert(c.artifactFilter.types.nonEmpty, "UpdateConfiguration must filter on some types")
val baseModules = modules map { m => restrictedCopy(m, true) }
// Adding list of explicit artifacts here.
val deps = baseModules.distinct flatMap classifiedArtifacts(classifiers, exclude, artifacts)
val base = restrictedCopy(id, true).copy(name = id.name + classifiers.mkString("$", "_", ""))
val module = new ivySbt.Module(InlineConfigurationWithExcludes(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala, configurations = confs))
- val upConf = new UpdateConfiguration(c.retrieve, true, c.logging)
+ // c.copy ensures c.types is preserved too
+ val upConf = c.copy(missingOk = true)
updateEither(module, upConf, uwconfig, logicalClock, depDir, log) match {
- case Right(r) => r
+ case Right(r) =>
+ // The artifacts that came from Ivy don't have their classifier set, let's set it according to
+ // FIXME: this is only done because IDE plugins depend on `classifier` to determine type. They
+ val typeClassifierMap: Map[String, String] =
+ ((sourceArtifactTypes.toIterable map (_ -> Artifact.SourceClassifier))
+ :: (docArtifactTypes.toIterable map (_ -> Artifact.DocClassifier)) :: Nil).flatten.toMap
+ r.substitute { (conf, mid, artFileSeq) =>
+ artFileSeq map {
+ case (art, f) =>
+ // Deduce the classifier from the type if no classifier is present already
+ art.copy(classifier = art.classifier orElse typeClassifierMap.get(art.`type`)) -> f
+ }
+ }
case Left(w) =>
throw w.resolveException
}
@@ -275,7 +304,7 @@ object IvyActions {
{
val arts = (artifacts collect { case (_, x, art, _) if sameModule(m, x) && art.classifier.isDefined => art }).distinct
if (arts.isEmpty) None
- else Some(m.copy(isTransitive = false, explicitArtifacts = arts))
+ else Some(intransitiveModuleWithExplicitArts(m, arts))
}
def hardcodedArtifacts = classifiedArtifacts(classifiers, exclude)(m)
explicitArtifacts orElse hardcodedArtifacts
@@ -284,8 +313,27 @@ object IvyActions {
{
val excluded = exclude getOrElse (restrictedCopy(m, false), Set.empty)
val included = classifiers filterNot excluded
- if (included.isEmpty) None else Some(m.copy(isTransitive = false, explicitArtifacts = classifiedArtifacts(m.name, included)))
+ if (included.isEmpty) None else {
+ Some(intransitiveModuleWithExplicitArts(module = m, arts = classifiedArtifacts(m.name, included)))
+ }
}
+
+ /**
+ * Explicitly set an "include all" rule (the default) because otherwise, if we declare ANY explicitArtifacts,
+ * [[org.apache.ivy.core.resolve.IvyNode#getArtifacts]] (in Ivy 2.3.0-rc1) will not merge in the descriptor's
+ * artifacts and will only keep the explicitArtifacts.
+ *
+ * Look for the comment saying {{{
+ * // and now we filter according to include rules
+ * }}}
+ * in `IvyNode`, which iterates on `includes`, which will ordinarily be empty because higher up, in {{{
+ * addAllIfNotNull(includes, usage.getDependencyIncludesSet(rootModuleConf));
+ * }}}
+ * `usage.getDependencyIncludesSet` returns null if there are no (explicit) include rules.
+ */
+ private def intransitiveModuleWithExplicitArts(module: ModuleID, arts: Seq[Artifact]): ModuleID =
+ module.copy(isTransitive = false, explicitArtifacts = arts, inclusions = InclExclRule.everything :: Nil)
+
def addExcluded(report: UpdateReport, classifiers: Seq[String], exclude: Map[ModuleID, Set[String]]): UpdateReport =
report.addMissing { id => classifiedArtifacts(id.name, classifiers filter getExcluded(id, exclude)) }
def classifiedArtifacts(name: String, classifiers: Seq[String]): Seq[Artifact] =
@@ -300,11 +348,12 @@ object IvyActions {
ModuleID(m.organization, m.name, m.revision, crossVersion = m.crossVersion, extraAttributes = m.extraAttributes, configurations = if (confs) m.configurations else None)
.branch(m.branchName)
- private[this] def resolve(logging: UpdateLogging.Value)(ivy: Ivy, module: DefaultModuleDescriptor, defaultConf: String): (ResolveReport, Option[ResolveException]) =
+ private[this] def resolve(logging: UpdateLogging.Value)(ivy: Ivy, module: DefaultModuleDescriptor, defaultConf: String, filter: ArtifactTypeFilter): (ResolveReport, Option[ResolveException]) =
{
val resolveOptions = new ResolveOptions
val resolveId = ResolveOptions.getDefaultResolveId(module)
resolveOptions.setResolveId(resolveId)
+ resolveOptions.setArtifactFilter(filter)
resolveOptions.setLog(ivyLogLevel(logging))
ResolutionCache.cleanModule(module.getModuleRevisionId, resolveId, ivy.getSettings.getResolutionCacheManager)
val resolveReport = ivy.resolve(module, resolveOptions)
diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/RichUpdateReport.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/RichUpdateReport.scala
index 61675fd2d..e4005ef21 100644
--- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/RichUpdateReport.scala
+++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/RichUpdateReport.scala
@@ -45,8 +45,8 @@ final class RichUpdateReport(report: UpdateReport) {
moduleReportMap { (configuration, modReport) =>
val newArtifacts = f(configuration, modReport.module, modReport.artifacts)
modReport.copy(
- artifacts = f(configuration, modReport.module, modReport.artifacts),
- missingArtifacts = Nil
+ artifacts = newArtifacts,
+ missingArtifacts = modReport.missingArtifacts
)
}
diff --git a/librarymanagement/src/main/scala/sbt/librarymanagement/Artifact.scala b/librarymanagement/src/main/scala/sbt/librarymanagement/Artifact.scala
index 2d174f035..8bf97bc05 100644
--- a/librarymanagement/src/main/scala/sbt/librarymanagement/Artifact.scala
+++ b/librarymanagement/src/main/scala/sbt/librarymanagement/Artifact.scala
@@ -8,10 +8,10 @@ import java.net.URL
import sbt.serialization._
final case class Artifact(name: String, `type`: String, extension: String, classifier: Option[String], configurations: Iterable[Configuration], url: Option[URL], extraAttributes: Map[String, String]) {
- def extra(attributes: (String, String)*) = Artifact(name, `type`, extension, classifier, configurations, url, extraAttributes ++ ModuleID.checkE(attributes))
+ def extra(attributes: (String, String)*) = copy(extraAttributes = extraAttributes ++ ModuleID.checkE(attributes))
}
-import Configurations.{ config, Docs, Optional, Pom, Sources, Test }
+import Configurations.{ config, Optional, Pom, Test }
object Artifact {
def apply(name: String): Artifact = Artifact(name, DefaultType, DefaultExtension, None, Nil, None)
@@ -30,12 +30,23 @@ object Artifact {
def javadoc(name: String) = classified(name, DocClassifier)
def pom(name: String) = Artifact(name, PomType, PomType, None, Pom :: Nil, None)
+ // Possible ivy artifact types such that sbt will treat those artifacts at sources / docs
+ val DefaultSourceTypes = Set("src", "source", "sources")
+ val DefaultDocTypes = Set("doc", "docs", "javadoc", "javadocs")
+
val DocClassifier = "javadoc"
val SourceClassifier = "sources"
+
+ val TestsClassifier = "tests"
+ // Artifact types used when:
+ // * artifacts are explicitly created for Maven dependency resolution (see updateClassifiers)
+ // * declaring artifacts as part of creating Ivy files.
val DocType = "doc"
val SourceType = "src"
val PomType = "pom"
- val TestsClassifier = "tests"
+
+ assert(DefaultDocTypes contains DocType)
+ assert(DefaultSourceTypes contains SourceType)
def extract(url: URL, default: String): String = extract(url.toString, default)
def extract(name: String, default: String): String =
@@ -62,16 +73,22 @@ object Artifact {
base + "-" + module.revision + classifierStr + "." + artifact.extension
}
- val classifierConfMap = Map(SourceClassifier -> Sources, DocClassifier -> Docs)
val classifierTypeMap = Map(SourceClassifier -> SourceType, DocClassifier -> DocType)
+ @deprecated("Configuration should not be decided from the classifier.", "1.0")
def classifierConf(classifier: String): Configuration =
if (classifier.startsWith(TestsClassifier))
Test
else
- classifierConfMap.getOrElse(classifier, Optional)
+ Optional
def classifierType(classifier: String): String = classifierTypeMap.getOrElse(classifier.stripPrefix(TestsClassifier + "-"), DefaultType)
+
+ /**
+ * Create a classified explicit artifact, to be used when trying to resolve sources|javadocs from Maven. This is
+ * necessary because those artifacts are not published in the Ivy generated from the Pom of the module in question.
+ * The artifact is created under the default configuration.
+ */
def classified(name: String, classifier: String): Artifact =
- Artifact(name, classifierType(classifier), DefaultExtension, Some(classifier), classifierConf(classifier) :: Nil, None)
+ Artifact(name, classifierType(classifier), DefaultExtension, Some(classifier), Nil, None)
private val optStringPickler = implicitly[Pickler[Option[String]]]
private val optStringUnpickler = implicitly[Unpickler[Option[String]]]
diff --git a/librarymanagement/src/main/scala/sbt/librarymanagement/Configuration.scala b/librarymanagement/src/main/scala/sbt/librarymanagement/Configuration.scala
index 50c432682..f860d4abc 100644
--- a/librarymanagement/src/main/scala/sbt/librarymanagement/Configuration.scala
+++ b/librarymanagement/src/main/scala/sbt/librarymanagement/Configuration.scala
@@ -10,7 +10,7 @@ object Configurations {
def default: Seq[Configuration] = defaultMavenConfigurations
def defaultMavenConfigurations: Seq[Configuration] = Seq(Compile, Runtime, Test, Provided, Optional)
def defaultInternal: Seq[Configuration] = Seq(CompileInternal, RuntimeInternal, TestInternal)
- def auxiliary: Seq[Configuration] = Seq(Sources, Docs, Pom)
+ def auxiliary: Seq[Configuration] = Seq(Pom)
def names(cs: Seq[Configuration]) = cs.map(_.name)
lazy val RuntimeInternal = optionalInternal(Runtime)
@@ -34,10 +34,8 @@ object Configurations {
lazy val Compile = config("compile")
lazy val IntegrationTest = config("it") extend (Runtime)
lazy val Provided = config("provided")
- lazy val Docs = config("docs")
lazy val Runtime = config("runtime") extend (Compile)
lazy val Test = config("test") extend (Runtime)
- lazy val Sources = config("sources")
lazy val System = config("system")
lazy val Optional = config("optional")
lazy val Pom = config("pom")
diff --git a/librarymanagement/src/main/scala/sbt/librarymanagement/IvyInterface.scala b/librarymanagement/src/main/scala/sbt/librarymanagement/IvyInterface.scala
index b582a51ed..964fe7447 100644
--- a/librarymanagement/src/main/scala/sbt/librarymanagement/IvyInterface.scala
+++ b/librarymanagement/src/main/scala/sbt/librarymanagement/IvyInterface.scala
@@ -8,6 +8,8 @@ import java.net.{ URI, URL }
import scala.xml.NodeSeq
import org.apache.ivy.plugins.resolver.{ DependencyResolver, IBiblioResolver }
import org.apache.ivy.util.url.CredentialsStore
+import org.apache.ivy.core.module.descriptor
+import org.apache.ivy.util.filter.{ Filter => IvyFilter }
import sbt.serialization._
/** Additional information about a project module */
@@ -25,10 +27,40 @@ final case class ScmInfo(browseUrl: URL, connection: String, devConnection: Opti
final case class Developer(id: String, name: String, email: String, url: URL)
-/** Rule to exclude unwanted dependencies pulled in transitively by a module. */
-final case class ExclusionRule(organization: String = "*", name: String = "*", artifact: String = "*", configurations: Seq[String] = Nil)
-object ExclusionRule {
- implicit val pickler: Pickler[ExclusionRule] with Unpickler[ExclusionRule] = PicklerUnpickler.generate[ExclusionRule]
+/**
+ * Rule to either:
+ *