Configurable conflict manager and corresponding documentation on conflictManager, dependencyOverrides, and force(). Fixes #603.

This commit is contained in:
Mark Harrah 2013-01-11 16:01:31 -05:00
parent b0a80efbda
commit a9ac6b3983
8 changed files with 152 additions and 6 deletions

View File

@ -20,7 +20,6 @@ import core.module.descriptor.{OverrideDependencyDescriptorMediator}
import core.module.id.{ArtifactId,ModuleId, ModuleRevisionId}
import core.resolve.{IvyNode, ResolveData, ResolvedModuleRevision}
import core.settings.IvySettings
import plugins.conflict.{ConflictManager, LatestCompatibleConflictManager, LatestConflictManager}
import plugins.latest.LatestRevisionStrategy
import plugins.matcher.PatternMatcher
import plugins.parser.m2.PomModuleDescriptorParser
@ -140,6 +139,7 @@ final class IvySbt(val configuration: IvyConfiguration)
{
import ic._
val moduleID = newConfiguredModuleID(module, moduleInfo, configurations)
IvySbt.setConflictManager(moduleID, conflictManager, ivy.getSettings)
val defaultConf = defaultConfiguration getOrElse Configurations.config(ModuleDescriptor.DEFAULT_CONFIGURATION)
log.debug("Using inline dependencies specified in Scala" + (if(ivyXML.isEmpty) "." else " and XML."))
@ -359,6 +359,14 @@ private object IvySbt
moduleID.setModuleArtifact(artifact)
moduleID.check()
}
private def setConflictManager(moduleID: DefaultModuleDescriptor, conflict: ConflictManager, is: IvySettings)
{
val mid = ModuleId.newInstance(conflict.organization, conflict.module)
val matcher = is.getMatcher(PatternMatcher.EXACT_OR_REGEXP)
val manager = is.getConflictManager(conflict.name)
moduleID.addConflictManager(mid, matcher, manager)
}
/** Converts the given sbt module id into an Ivy ModuleRevisionId.*/
def toID(m: ModuleID) =
{

View File

@ -74,7 +74,7 @@ final case class PomConfiguration(file: File, ivyScala: Option[IvyScala], valida
{
def noScala = copy(ivyScala = None)
}
final case class InlineConfiguration(module: ModuleID, moduleInfo: ModuleInfo, dependencies: Seq[ModuleID], overrides: Set[ModuleID] = Set.empty, ivyXML: NodeSeq = NodeSeq.Empty, configurations: Seq[Configuration] = Nil, defaultConfiguration: Option[Configuration] = None, ivyScala: Option[IvyScala] = None, validate: Boolean = false) extends ModuleSettings
final case class InlineConfiguration(module: ModuleID, moduleInfo: ModuleInfo, dependencies: Seq[ModuleID], overrides: Set[ModuleID] = Set.empty, ivyXML: NodeSeq = NodeSeq.Empty, configurations: Seq[Configuration] = Nil, defaultConfiguration: Option[Configuration] = None, ivyScala: Option[IvyScala] = None, validate: Boolean = false, conflictManager: ConflictManager = ConflictManager.default) extends ModuleSettings
{
def withConfigurations(configurations: Seq[Configuration]) = copy(configurations = configurations)
def noScala = copy(ivyScala = None)

View File

@ -10,7 +10,7 @@ import org.apache.ivy.plugins.resolver.{DependencyResolver, IBiblioResolver}
import org.apache.ivy.util.url.CredentialsStore
/** Additional information about a project module */
case class ModuleInfo(nameFormal: String, description: String = "", homepage: Option[URL] = None, startYear: Option[Int] = None, licenses: Seq[(String, URL)] = Nil, organizationName: String = "", organizationHomepage: Option[URL] = None, scmInfo: Option[ScmInfo] = None)
final case class ModuleInfo(nameFormal: String, description: String = "", homepage: Option[URL] = None, startYear: Option[Int] = None, licenses: Seq[(String, URL)] = Nil, organizationName: String = "", organizationHomepage: Option[URL] = None, scmInfo: Option[ScmInfo] = None)
{
def formally(name: String) = copy(nameFormal = name)
def describing(desc: String, home: Option[URL]) = copy(description = desc, homepage = home)
@ -19,10 +19,10 @@ case class ModuleInfo(nameFormal: String, description: String = "", homepage: Op
}
/** Basic SCM information for a project module */
case class ScmInfo(browseUrl: URL, connection: String, devConnection: Option[String] = None)
final case class ScmInfo(browseUrl: URL, connection: String, devConnection: Option[String] = None)
/** Rule to exclude unwanted dependencies pulled in transitively by a module. */
case class ExclusionRule(organization: String = "*", name: String = "*", artifact: String = "*", configurations: Seq[String] = Nil)
final case class ExclusionRule(organization: String = "*", name: String = "*", artifact: String = "*", configurations: Seq[String] = Nil)
final case class ModuleConfiguration(organization: String, name: String, revision: String, resolver: Resolver)
object ModuleConfiguration
@ -30,3 +30,15 @@ object ModuleConfiguration
def apply(org: String, resolver: Resolver): ModuleConfiguration = apply(org, "*", "*", resolver)
def apply(org: String, name: String, resolver: Resolver): ModuleConfiguration = ModuleConfiguration(org, name, "*", resolver)
}
final case class ConflictManager(name: String, organization: String = "*", module: String = "*")
/** See http://ant.apache.org/ivy/history/latest-milestone/settings/conflict-managers.html for details of the different conflict managers.*/
object ConflictManager {
val all = ConflictManager("all")
val latestTime = ConflictManager("latest-time")
val latestRevision = ConflictManager("latest-revision")
val latestCompatible = ConflictManager("latest-compatible")
val strict = ConflictManager("strict")
val default = latestRevision
}

View File

@ -119,6 +119,7 @@ object Defaults extends BuildCommon
artifactClassifier in packageSrc :== Some(SourceClassifier),
artifactClassifier in packageDoc :== Some(DocClassifier),
checksums := Classpaths.bootChecksums(appConfiguration.value),
conflictManager := ConflictManager.default,
pomExtra :== NodeSeq.Empty,
pomPostProcess :== idFun,
pomAllRepositories :== false,
@ -961,7 +962,7 @@ object Classpaths
new IvySbt(conf)
}
def moduleSettings0: Initialize[Task[ModuleSettings]] = Def.task {
new InlineConfiguration(projectID.value, projectInfo.value, allDependencies.value, dependencyOverrides.value, ivyXML.value, ivyConfigurations.value, defaultConfiguration.value, ivyScala.value, ivyValidate.value)
new InlineConfiguration(projectID.value, projectInfo.value, allDependencies.value, dependencyOverrides.value, ivyXML.value, ivyConfigurations.value, defaultConfiguration.value, ivyScala.value, ivyValidate.value, conflictManager.value)
}
def sbtClassifiersTasks = inTask(updateSbtClassifiers)(Seq(

View File

@ -313,6 +313,7 @@ object Keys
val classifiersModule = TaskKey[GetClassifiersModule]("classifiers-module", rank = CTask)
val conflictWarning = SettingKey[ConflictWarning]("conflict-warning", "Configures warnings for conflicts in dependency management.", CSetting)
val conflictManager = SettingKey[ConflictManager]("conflict-manager", "Selects the conflict manager to use for dependency management.", CSetting)
val autoScalaLibrary = SettingKey[Boolean]("auto-scala-library", "Adds a dependency on scala-library if true.", ASetting)
val managedScalaInstance = SettingKey[Boolean]("managed-scala-instance", "Automatically obtains Scala tools as managed dependencies if true.", BSetting)
val sbtResolver = SettingKey[Resolver]("sbt-resolver", "Provides a resolver for obtaining sbt as a dependency.", BMinusSetting)

View File

@ -0,0 +1,8 @@
libraryDependencies ++= Seq(
"org.spark-project" %% "spark-core" % "0.5.1",
"log4j" % "log4j" % "1.2.17"
)
scalaVersion := "2.9.2"
name := "conflict-manager-test"

View File

@ -0,0 +1,3 @@
> update
> set conflictManager := ConflictManager.strict
-> update

View File

@ -399,6 +399,119 @@ The default value is:
checksums := Seq("sha1", "md5")
Conflict Management
~~~~~~~~~~~~~~~~~~~
The conflict manager decides what to do when dependency resolution brings in different versions of the same library.
By default, the latest revision is selected.
This can be changed by setting ``conflictManager``, which has type `ConflictManager <../../api/sbt/ConflictManager.html>`_.
See the `Ivy documentation <http://ant.apache.org/ivy/history/latest-milestone/settings/conflict-managers.html>`_ for details on the different conflict managers.
For example, to specify that no conflicts are allowed,
::
conflictManager := ConflictManager.strict
With this set, any conflicts will generate an error.
To resolve a conflict,
* configure a dependency override if the conflict is for a transitive dependency
* force the revision if it is a direct dependency
Both are explained in the following sections.
Forcing a revision
~~~~~~~~~~~~~~~~~~
The following direct dependencies will introduce a conflict on the log4j version because spark requires log4j 1.2.16.
::
libraryDependencies ++= Seq(
"org.spark-project" %% "spark-core" % "0.5.1",
"log4j" % "log4j" % "1.2.14"
)
The default conflict manager will select the newer version of log4j, 1.2.16.
This can be confirmed in the output of `show update`, which shows the newer version as being selected and the older version as not selected:
::
> show update
[info] compile:
[info] log4j:log4j:1.2.16: ...
...
[info] (EVICTED) log4j:log4j:1.2.14
...
To say that we prefer the version we've specified over the version from indirect dependencies, use ``force()``:
::
libraryDependencies ++= Seq(
"org.spark-project" %% "spark-core" % "0.5.1",
"log4j" % "log4j" % "1.2.14" force()
)
The output of ``show update`` is now reversed:
::
> show update
[info] compile:
[info] log4j:log4j:1.2.14: ...
...
[info] (EVICTED) log4j:log4j:1.2.16
...
**Note:** this is an Ivy-only feature and cannot be included in a published pom.xml.
Forcing a revision without introducing a dependency
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use of the ``force()`` method described in the previous section requires having a direct dependency.
However, it may be desirable to force a revision without introducing that direct dependency.
Ivy provides overrides for this and in sbt, overrides are configured in sbt with the ``dependencyOverrides`` setting, which is a set of ``ModuleID``s.
For example, the following dependency definitions conflict because spark uses log4j 1.2.16 and scalaxb uses log4j 1.2.17:
::
libraryDependencies ++= Seq(
"org.spark-project" %% "spark-core" % "0.5.1",
"org.scalaxb" %% "scalaxb" % "1.0.0"
)
The default conflict manager chooses the latest revision of log4j, 1.2.17:
::
> show update
[info] compile:
[info] log4j:log4j:1.2.17: ...
...
[info] (EVICTED) log4j:log4j:1.2.16
...
To change the version selected, add an override:
::
dependencyOverrides += "log4j" % "log4j" % "1.2.16"
This will not add a direct dependency on log4j, but will force the revision to be 1.2.16.
This is confirmed by the output of ``show update``:
::
> show update
[info] compile:
[info] log4j:log4j:1.2.16
...
**Note:** this is an Ivy-only feature and will not be included in a published pom.xml.
.. _packaging-pom:
packaging="pom"