diff --git a/core/shared/src/main/scala/coursier/core/Resolution.scala b/core/shared/src/main/scala/coursier/core/Resolution.scala index 3b19714eb..fa4203bdd 100644 --- a/core/shared/src/main/scala/coursier/core/Resolution.scala +++ b/core/shared/src/main/scala/coursier/core/Resolution.scala @@ -163,25 +163,34 @@ object Resolution { * Returns the conflicted dependencies, and the merged others. */ def merge( - dependencies: TraversableOnce[Dependency] + dependencies: TraversableOnce[Dependency], + forceVersions: Map[Module, String] ): (Seq[Dependency], Seq[Dependency]) = { val mergedByModVer = dependencies .toList .groupBy(dep => dep.module) - .mapValues { deps => - if (deps.lengthCompare(1) == 0) \/-(deps) - else { - val versions = deps - .map(_.version) - .distinct - val versionOpt = mergeVersions(versions) - - versionOpt match { - case Some(version) => - \/-(deps.map(dep => dep.copy(version = version))) + .map { case (module, deps) => + module -> { + forceVersions.get(module) match { case None => - -\/(deps) + if (deps.lengthCompare(1) == 0) \/-(deps) + else { + val versions = deps + .map(_.version) + .distinct + val versionOpt = mergeVersions(versions) + + versionOpt match { + case Some(version) => + \/-(deps.map(dep => dep.copy(version = version))) + case None => + -\/(deps) + } + } + + case Some(forcedVersion) => + \/-(deps.map(dep => dep.copy(version = forcedVersion))) } } } @@ -380,6 +389,7 @@ object Resolution { case class Resolution( rootDependencies: Set[Dependency], dependencies: Set[Dependency], + forceVersions: Map[Module, String], conflicts: Set[Dependency], projectCache: Map[Resolution.ModuleVersion, (Artifact.Source, Project)], errorCache: Map[Resolution.ModuleVersion, Seq[String]], @@ -426,9 +436,10 @@ case class Resolution( * the dependencies. */ def nextDependenciesAndConflicts: (Seq[Dependency], Seq[Dependency]) = + // TODO Provide the modules whose version was forced by dependency overrides too merge( - rootDependencies.map(withDefaultScope) ++ dependencies ++ - transitiveDependencies + rootDependencies.map(withDefaultScope) ++ dependencies ++ transitiveDependencies, + forceVersions ) /** diff --git a/core/shared/src/main/scala/coursier/package.scala b/core/shared/src/main/scala/coursier/package.scala index d060d0b4b..6803f43ed 100644 --- a/core/shared/src/main/scala/coursier/package.scala +++ b/core/shared/src/main/scala/coursier/package.scala @@ -63,6 +63,7 @@ package object coursier { def apply( rootDependencies: Set[Dependency] = Set.empty, dependencies: Set[Dependency] = Set.empty, + forceVersions: Map[Module, String] = Map.empty, conflicts: Set[Dependency] = Set.empty, projectCache: Map[ModuleVersion, (Artifact.Source, Project)] = Map.empty, errorCache: Map[ModuleVersion, Seq[String]] = Map.empty, @@ -72,6 +73,7 @@ package object coursier { core.Resolution( rootDependencies, dependencies, + forceVersions, conflicts, projectCache, errorCache, diff --git a/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala b/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala index 5b17d8e1e..fd9d0ea4d 100644 --- a/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala +++ b/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala @@ -9,12 +9,15 @@ import coursier.test.compatibility._ object ResolutionTests extends TestSuite { - def resolve0(deps: Set[Dependency], filter: Option[Dependency => Boolean] = None) = { - Resolution(deps, filter = filter) + def resolve0( + deps: Set[Dependency], + filter: Option[Dependency => Boolean] = None, + forceVersions: Map[Module, String] = Map.empty + ) = + Resolution(deps, filter = filter, forceVersions = forceVersions) .process .run(Fetch.default(repositories)) .runF - } implicit class ProjectOps(val p: Project) extends AnyVal { def kv: (ModuleVersion, (Artifact.Source, Project)) = p.moduleVersion -> (testRepository.source, p) @@ -134,9 +137,16 @@ object ResolutionTests extends TestSuite { Project(Module("an-org", "a-name"), "1.0"), + Project(Module("an-org", "a-name"), "1.2"), + Project(Module("an-org", "a-lib"), "1.0", Seq(Dependency(Module("an-org", "a-name"), "1.0"))), + Project(Module("an-org", "a-lib"), "1.1"), + + Project(Module("an-org", "a-lib"), "1.2", + Seq(Dependency(Module("an-org", "a-name"), "1.2"))), + Project(Module("an-org", "another-lib"), "1.0", Seq(Dependency(Module("an-org", "a-name"), "1.0"))), @@ -144,7 +154,15 @@ object ResolutionTests extends TestSuite { Project(Module("an-org", "an-app"), "1.0", Seq( Dependency(Module("an-org", "a-lib"), "1.0", exclusions = Set(("an-org", "a-name"))), - Dependency(Module("an-org", "another-lib"), "1.0", optional = true))) + Dependency(Module("an-org", "another-lib"), "1.0", optional = true))), + + Project(Module("an-org", "an-app"), "1.1", + Seq( + Dependency(Module("an-org", "a-lib"), "1.1"))), + + Project(Module("an-org", "an-app"), "1.2", + Seq( + Dependency(Module("an-org", "a-lib"), "1.2"))) ) val projectsMap = projects.map(p => p.moduleVersion -> p).toMap @@ -483,6 +501,86 @@ object ResolutionTests extends TestSuite { } } + 'dependencyOverrides - { + * - { + async { + val deps = Set( + Dependency(Module("an-org", "a-name"), "1.1")) + val depOverrides = Map( + Module("an-org", "a-name") -> "1.0") + + val res = await(resolve0( + deps, + forceVersions = depOverrides, + filter = Some(_.scope == Scope.Compile) + )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + + val expected = Resolution( + rootDependencies = deps, + dependencies = Set( + Dependency(Module("an-org", "a-name"), "1.0") + ).map(_.withCompileScope), + forceVersions = depOverrides + ) + + assert(res == expected) + } + } + + * - { + async { + val deps = Set( + Dependency(Module("an-org", "an-app"), "1.1")) + val depOverrides = Map( + Module("an-org", "a-lib") -> "1.0") + + val res = await(resolve0( + deps, + forceVersions = depOverrides, + filter = Some(_.scope == Scope.Compile) + )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + + val expected = Resolution( + rootDependencies = deps, + dependencies = Set( + Dependency(Module("an-org", "an-app"), "1.1"), + Dependency(Module("an-org", "a-lib"), "1.0"), + Dependency(Module("an-org", "a-name"), "1.0") + ).map(_.withCompileScope), + forceVersions = depOverrides + ) + + assert(res == expected) + } + } + + * - { + async { + val deps = Set( + Dependency(Module("an-org", "an-app"), "1.2")) + val depOverrides = Map( + Module("an-org", "a-lib") -> "1.1") + + val res = await(resolve0( + deps, + forceVersions = depOverrides, + filter = Some(_.scope == Scope.Compile) + )).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty) + + val expected = Resolution( + rootDependencies = deps, + dependencies = Set( + Dependency(Module("an-org", "an-app"), "1.2"), + Dependency(Module("an-org", "a-lib"), "1.1") + ).map(_.withCompileScope), + forceVersions = depOverrides + ) + + assert(res == expected) + } + } + } + 'parts{ 'propertySubstitution{ val res =