From d329f3113755a4f310bd67eed84ad20516d11a2b Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 2 Aug 2018 15:02:41 +0200 Subject: [PATCH] Allow to force the value of Maven properties during resolution (#886) --- .../main/scala-2.12/coursier/cli/Helper.scala | 13 +++++++- .../coursier/cli/options/CommonOptions.scala | 3 ++ .../main/scala/coursier/core/Resolution.scala | 25 +++++++++++++-- .../coursier/core/ResolutionProcess.scala | 13 +++++--- .../src/main/scala/coursier/package.scala | 6 ++-- .../scala/coursier/test/ResolutionTests.scala | 31 +++++++++++++++++-- 6 files changed, 79 insertions(+), 12 deletions(-) diff --git a/cli/src/main/scala-2.12/coursier/cli/Helper.scala b/cli/src/main/scala-2.12/coursier/cli/Helper.scala index 826434e83..ed6ff4740 100644 --- a/cli/src/main/scala-2.12/coursier/cli/Helper.scala +++ b/cli/src/main/scala-2.12/coursier/cli/Helper.scala @@ -318,6 +318,16 @@ class Helper( val userEnabledProfiles = profile.toSet + val forcedProperties = forceProperty + .map { s => + s.split("=", 2) match { + case Array(k, v) => k -> v + case _ => + sys.error(s"Malformed forced property argument: $s") + } + } + .toMap + val startRes = Resolution( allDependencies.toSet, forceVersions = forceVersions, @@ -325,7 +335,8 @@ class Helper( userActivations = if (userEnabledProfiles.isEmpty) None else Some(userEnabledProfiles.iterator.map(p => if (p.startsWith("!")) p.drop(1) -> false else p -> true).toMap), - mapDependencies = if (typelevel) Some(Typelevel.swap(_)) else None + mapDependencies = if (typelevel) Some(Typelevel.swap(_)) else None, + forceProperties = forcedProperties ) val logger = diff --git a/cli/src/main/scala-2.12/coursier/cli/options/CommonOptions.scala b/cli/src/main/scala-2.12/coursier/cli/options/CommonOptions.scala index 50aecad98..6ee0456de 100644 --- a/cli/src/main/scala-2.12/coursier/cli/options/CommonOptions.scala +++ b/cli/src/main/scala-2.12/coursier/cli/options/CommonOptions.scala @@ -41,6 +41,9 @@ final case class CommonOptions( @Value("organization:name:forcedVersion") @Short("V") forceVersion: List[String] = Nil, + @Help("Force property in POM files") + @Value("name=value") + forceProperty: List[String] = Nil, @Help("Exclude module") @Value("organization:name") @Short("E") diff --git a/core/shared/src/main/scala/coursier/core/Resolution.scala b/core/shared/src/main/scala/coursier/core/Resolution.scala index aa1de0772..f2c7b0ca4 100644 --- a/core/shared/src/main/scala/coursier/core/Resolution.scala +++ b/core/shared/src/main/scala/coursier/core/Resolution.scala @@ -555,7 +555,8 @@ final case class Resolution( osInfo: Activation.Os, jdkVersion: Option[Version], userActivations: Option[Map[String, Boolean]], - mapDependencies: Option[Dependency => Dependency] + mapDependencies: Option[Dependency => Dependency], + forceProperties: Map[String, String] ) { def copyWithCache( @@ -563,13 +564,13 @@ final case class Resolution( dependencies: Set[Dependency] = dependencies, forceVersions: Map[Module, String] = forceVersions, conflicts: Set[Dependency] = conflicts, - projectCache: Map[Resolution.ModuleVersion, (Artifact.Source, Project)] = projectCache, errorCache: Map[Resolution.ModuleVersion, Seq[String]] = errorCache, filter: Option[Dependency => Boolean] = filter, osInfo: Activation.Os = osInfo, jdkVersion: Option[Version] = jdkVersion, userActivations: Option[Map[String, Boolean]] = userActivations // don't allow changing mapDependencies here - that would invalidate finalDependenciesCache + // don't allow changing projectCache here - use addToProjectCache that takes forceProperties into account ): Resolution = copy( rootDependencies, @@ -585,6 +586,26 @@ final case class Resolution( userActivations ) + def addToProjectCache(projects: (Resolution.ModuleVersion, (Artifact.Source, Project))*): Resolution = { + + val duplicates = projects + .collect { + case (modVer, _) if projectCache.contains(modVer) => + modVer + } + + assert(duplicates.isEmpty, s"Projects already added in resolution: ${duplicates.mkString(", ")}") + + copy( + finalDependenciesCache = finalDependenciesCache ++ finalDependenciesCache0.asScala, + projectCache = projectCache ++ projects.map { + case (modVer, (s, p)) => + val p0 = withDependencyManagement(p.copy(properties = p.properties.filter(kv => !forceProperties.contains(kv._1)) ++ forceProperties)) + (modVer, (s, p0)) + } + ) + } + import Resolution._ private[core] val finalDependenciesCache0 = new ConcurrentHashMap[Dependency, Seq[Dependency]] diff --git a/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala b/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala index 274ffa100..04c15b977 100644 --- a/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala +++ b/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala @@ -73,7 +73,12 @@ final case class Missing( def cont0(res: Resolution): ResolutionProcess = { - val depMgmtMissing0 = successes.map { + val remainingSuccesses = successes.filter { + case (modVer, _) => + !res.projectCache.contains(modVer) + } + + val depMgmtMissing0 = remainingSuccesses.map { case elem @ (_, (_, proj)) => elem -> res.dependencyManagementMissing(proj) } @@ -107,10 +112,8 @@ final case class Missing( val res0 = orderedSuccesses.foldLeft(res) { case (acc, (modVer0, (source, proj))) => - acc.copyWithCache( - projectCache = acc.projectCache + ( - modVer0 -> (source, acc.withDependencyManagement(proj)) - ) + acc.addToProjectCache( + modVer0 -> (source, proj) ) } diff --git a/core/shared/src/main/scala/coursier/package.scala b/core/shared/src/main/scala/coursier/package.scala index 08ebfff44..277cdad98 100644 --- a/core/shared/src/main/scala/coursier/package.scala +++ b/core/shared/src/main/scala/coursier/package.scala @@ -79,7 +79,8 @@ package object coursier { osInfo: Activation.Os = Activation.Os.fromProperties(sys.props.toMap), jdkVersion: Option[Version] = sys.props.get("java.version").flatMap(Parse.version), userActivations: Option[Map[String, Boolean]] = None, - mapDependencies: Option[Dependency => Dependency] = None + mapDependencies: Option[Dependency => Dependency] = None, + forceProperties: Map[String, String] = Map.empty ): Resolution = core.Resolution( rootDependencies, @@ -93,7 +94,8 @@ package object coursier { osInfo, jdkVersion, userActivations, - mapDependencies + mapDependencies, + forceProperties ) } diff --git a/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala b/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala index b8c21b966..f27e72c50 100644 --- a/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala +++ b/tests/shared/src/test/scala/coursier/test/ResolutionTests.scala @@ -13,9 +13,10 @@ object ResolutionTests extends TestSuite { def resolve0( deps: Set[Dependency], filter: Option[Dependency => Boolean] = None, - forceVersions: Map[Module, String] = Map.empty + forceVersions: Map[Module, String] = Map.empty, + forceProperties: Map[String, String] = Map.empty ) = - Resolution(deps, filter = filter, forceVersions = forceVersions) + Resolution(deps, filter = filter, forceVersions = forceVersions, forceProperties = forceProperties) .process .run(Platform.fetch(repositories)) .future() @@ -649,6 +650,32 @@ object ResolutionTests extends TestSuite { assert(res == expected) } } + + 'forcedProperties - { + async { + val deps = Set( + Dependency(Module("com.github.dummy", "libb"), "0.5.4") + ) + + val forceProperties = Map( + "special" -> "false" + ) + + val res = await( + resolve0(deps, forceProperties = forceProperties) + ).clearCaches + + val expected = Resolution( + rootDependencies = deps, + dependencies = Set( + Dependency(Module("com.github.dummy", "libb"), "0.5.4") + ).map(_.withCompileScope), + forceProperties = forceProperties + ) + + assert(res == expected) + } + } } }