mirror of https://github.com/sbt/sbt.git
Refactor exclusions, add partial orders / minifications
This commit is contained in:
parent
e6ec32f33a
commit
54338f7b04
|
|
@ -0,0 +1,53 @@
|
||||||
|
package coursier.core
|
||||||
|
|
||||||
|
object Exclusions {
|
||||||
|
|
||||||
|
def partition(exclusions: Set[(String, String)]): (Boolean, Set[String], Set[String], Set[(String, String)]) = {
|
||||||
|
|
||||||
|
val (wildCards, remaining) = exclusions
|
||||||
|
.partition{case (org, name) => org == "*" || name == "*" }
|
||||||
|
|
||||||
|
val all = wildCards
|
||||||
|
.contains(("*", "*"))
|
||||||
|
|
||||||
|
val excludeByOrg = wildCards
|
||||||
|
.collect{case (org, "*") if org != "*" => org }
|
||||||
|
val excludeByName = wildCards
|
||||||
|
.collect{case ("*", name) if name != "*" => name }
|
||||||
|
|
||||||
|
(all, excludeByOrg, excludeByName, remaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply(exclusions: Set[(String, String)]): (String, String) => Boolean = {
|
||||||
|
|
||||||
|
val (all, excludeByOrg, excludeByName, remaining) = partition(exclusions)
|
||||||
|
|
||||||
|
if (all) (_, _) => false
|
||||||
|
else
|
||||||
|
(org, name) => {
|
||||||
|
!excludeByName(name) &&
|
||||||
|
!excludeByOrg(org) &&
|
||||||
|
!remaining((org, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def minimize(exclusions: Set[(String, String)]): Set[(String, String)] = {
|
||||||
|
|
||||||
|
val (all, excludeByOrg, excludeByName, remaining) = partition(exclusions)
|
||||||
|
|
||||||
|
if (all) Set(("*", "*"))
|
||||||
|
else {
|
||||||
|
val filteredRemaining = remaining
|
||||||
|
.filter{case (org, name) =>
|
||||||
|
!excludeByOrg(org) &&
|
||||||
|
!excludeByName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
excludeByOrg.map((_, "*")) ++
|
||||||
|
excludeByName.map(("*", _)) ++
|
||||||
|
filteredRemaining
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
package coursier.core
|
||||||
|
|
||||||
|
object Orders {
|
||||||
|
|
||||||
|
/** Minimal ad-hoc partial order */
|
||||||
|
trait PartialOrder[A] {
|
||||||
|
/**
|
||||||
|
* x < y: Some(neg. integer)
|
||||||
|
* x == y: Some(0)
|
||||||
|
* x > y: Some(pos. integer)
|
||||||
|
* x, y not related: None
|
||||||
|
*/
|
||||||
|
def cmp(x: A, y: A): Option[Int]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only relations:
|
||||||
|
* Compile < Runtime < Test
|
||||||
|
*/
|
||||||
|
implicit val mavenScopePartialOrder: PartialOrder[Scope] =
|
||||||
|
new PartialOrder[Scope] {
|
||||||
|
val higher = Map[Scope, Set[Scope]](
|
||||||
|
Scope.Compile -> Set(Scope.Runtime, Scope.Test),
|
||||||
|
Scope.Runtime -> Set(Scope.Test)
|
||||||
|
)
|
||||||
|
|
||||||
|
def cmp(x: Scope, y: Scope) =
|
||||||
|
if (x == y) Some(0)
|
||||||
|
else if (higher.get(x).exists(_(y))) Some(-1)
|
||||||
|
else if (higher.get(y).exists(_(x))) Some(1)
|
||||||
|
else None
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Non-optional < optional */
|
||||||
|
implicit val optionalPartialOrder: PartialOrder[Boolean] =
|
||||||
|
new PartialOrder[Boolean] {
|
||||||
|
def cmp(x: Boolean, y: Boolean) =
|
||||||
|
Some(
|
||||||
|
if (x == y) 0
|
||||||
|
else if (x) 1
|
||||||
|
else -1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclusions partial order.
|
||||||
|
*
|
||||||
|
* x <= y iff all that x excludes is also excluded by y.
|
||||||
|
* x and y not related iff x excludes some elements not excluded by y AND
|
||||||
|
* y excludes some elements not excluded by x.
|
||||||
|
*
|
||||||
|
* In particular, no exclusions <= anything <= Set(("*", "*"))
|
||||||
|
*/
|
||||||
|
implicit val exclusionsPartialOrder: PartialOrder[Set[(String, String)]] =
|
||||||
|
new PartialOrder[Set[(String, String)]] {
|
||||||
|
def boolCmp(a: Boolean, b: Boolean) = (a, b) match {
|
||||||
|
case (true, true) => Some(0)
|
||||||
|
case (true, false) => Some(1)
|
||||||
|
case (false, true) => Some(-1)
|
||||||
|
case (false, false) => None
|
||||||
|
}
|
||||||
|
|
||||||
|
def cmp(x: Set[(String, String)], y: Set[(String, String)]) = {
|
||||||
|
val (xAll, xExcludeByOrg1, xExcludeByName1, xRemaining0) = Exclusions.partition(x)
|
||||||
|
val (yAll, yExcludeByOrg1, yExcludeByName1, yRemaining0) = Exclusions.partition(y)
|
||||||
|
|
||||||
|
boolCmp(xAll, yAll).orElse {
|
||||||
|
def filtered(e: Set[(String, String)]) =
|
||||||
|
e.filter{case (org, name) =>
|
||||||
|
!xExcludeByOrg1(org) && !yExcludeByOrg1(org) &&
|
||||||
|
!xExcludeByName1(name) && !yExcludeByName1(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
def removeIntersection[T](a: Set[T], b: Set[T]) =
|
||||||
|
(a -- b, b -- a)
|
||||||
|
|
||||||
|
def allEmpty(set: Set[_]*) = set.forall(_.isEmpty)
|
||||||
|
|
||||||
|
val (xRemaining1, yRemaining1) =
|
||||||
|
(filtered(xRemaining0), filtered(yRemaining0))
|
||||||
|
|
||||||
|
val (xProperRemaining, yProperRemaining) =
|
||||||
|
removeIntersection(xRemaining1, yRemaining1)
|
||||||
|
|
||||||
|
val (onlyXExcludeByOrg, onlyYExcludeByOrg) =
|
||||||
|
removeIntersection(xExcludeByOrg1, yExcludeByOrg1)
|
||||||
|
|
||||||
|
val (onlyXExcludeByName, onlyYExcludeByName) =
|
||||||
|
removeIntersection(xExcludeByName1, yExcludeByName1)
|
||||||
|
|
||||||
|
val (noXProper, noYProper) = (
|
||||||
|
allEmpty(xProperRemaining, onlyXExcludeByOrg, onlyXExcludeByName),
|
||||||
|
allEmpty(yProperRemaining, onlyYExcludeByOrg, onlyYExcludeByName)
|
||||||
|
)
|
||||||
|
|
||||||
|
boolCmp(noYProper, noXProper) // order matters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assume all dependencies have same `module`, `version`, and `artifact`; see `minDependencies`
|
||||||
|
* if they don't.
|
||||||
|
*/
|
||||||
|
def minDependenciesUnsafe(dependencies: Set[Dependency]): Set[Dependency] = {
|
||||||
|
val groupedDependencies = dependencies
|
||||||
|
.groupBy(dep => (dep.optional, dep.scope))
|
||||||
|
.toList
|
||||||
|
|
||||||
|
val remove =
|
||||||
|
for {
|
||||||
|
List(((xOpt, xScope), xDeps), ((yOpt, yScope), yDeps)) <- groupedDependencies.combinations(2)
|
||||||
|
optCmp <- optionalPartialOrder.cmp(xOpt, yOpt).iterator
|
||||||
|
scopeCmp <- mavenScopePartialOrder.cmp(xScope, yScope).iterator
|
||||||
|
if optCmp*scopeCmp >= 0
|
||||||
|
xDep <- xDeps.iterator
|
||||||
|
yDep <- yDeps.iterator
|
||||||
|
exclCmp <- exclusionsPartialOrder.cmp(xDep.exclusions, yDep.exclusions).iterator
|
||||||
|
if optCmp*exclCmp >= 0
|
||||||
|
if scopeCmp*exclCmp >= 0
|
||||||
|
xIsMin = optCmp < 0 || scopeCmp < 0 || exclCmp < 0
|
||||||
|
yIsMin = optCmp > 0 || scopeCmp > 0 || exclCmp > 0
|
||||||
|
if xIsMin || yIsMin // should be always true, unless xDep == yDep, which shouldn't happen
|
||||||
|
} yield if (xIsMin) yDep else xDep
|
||||||
|
|
||||||
|
dependencies -- remove
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minified representation of `dependencies`.
|
||||||
|
*
|
||||||
|
* The returned set brings exactly the same things as `dependencies`, with no redundancy.
|
||||||
|
*/
|
||||||
|
def minDependencies(dependencies: Set[Dependency]): Set[Dependency] = {
|
||||||
|
dependencies
|
||||||
|
.groupBy(_.copy(scope = Scope.Other(""), exclusions = Set.empty, optional = false))
|
||||||
|
.mapValues(minDependenciesUnsafe)
|
||||||
|
.valuesIterator
|
||||||
|
.fold(Set.empty)(_ ++ _)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -223,34 +223,6 @@ object Resolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Addition of exclusions. A module is excluded by the result if it is excluded
|
|
||||||
* by `first`, by `second`, or by both.
|
|
||||||
*/
|
|
||||||
def exclusionsAdd(first: Set[(String, String)],
|
|
||||||
second: Set[(String, String)]): Set[(String, String)] = {
|
|
||||||
|
|
||||||
val (firstAll, firstNonAll) = first.partition{case ("*", "*") => true; case _ => false }
|
|
||||||
val (secondAll, secondNonAll) = second.partition{case ("*", "*") => true; case _ => false }
|
|
||||||
|
|
||||||
if (firstAll.nonEmpty || secondAll.nonEmpty) Set(("*", "*"))
|
|
||||||
else {
|
|
||||||
val firstOrgWildcards = firstNonAll.collect{ case ("*", name) => name }
|
|
||||||
val firstNameWildcards = firstNonAll.collect{ case (org, "*") => org }
|
|
||||||
val secondOrgWildcards = secondNonAll.collect{ case ("*", name) => name }
|
|
||||||
val secondNameWildcards = secondNonAll.collect{ case (org, "*") => org }
|
|
||||||
|
|
||||||
val orgWildcards = firstOrgWildcards ++ secondOrgWildcards
|
|
||||||
val nameWildcards = firstNameWildcards ++ secondNameWildcards
|
|
||||||
|
|
||||||
val firstRemaining = firstNonAll.filter{ case (org, name) => org != "*" && name != "*" }
|
|
||||||
val secondRemaining = secondNonAll.filter{ case (org, name) => org != "*" && name != "*" }
|
|
||||||
|
|
||||||
val remaining = (firstRemaining ++ secondRemaining).filterNot{case (org, name) => orgWildcards(name) || nameWildcards(org) }
|
|
||||||
|
|
||||||
orgWildcards.map(name => ("*", name)) ++ nameWildcards.map(org => (org, "*")) ++ remaining
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def withDefaultScope(dep: Dependency): Dependency =
|
def withDefaultScope(dep: Dependency): Dependency =
|
||||||
if (dep.scope.name.isEmpty) dep.copy(scope = Scope.Compile)
|
if (dep.scope.name.isEmpty) dep.copy(scope = Scope.Compile)
|
||||||
|
|
@ -262,22 +234,12 @@ object Resolver {
|
||||||
def withExclusions(dependencies: Seq[Dependency],
|
def withExclusions(dependencies: Seq[Dependency],
|
||||||
exclusions: Set[(String, String)]): Seq[Dependency] = {
|
exclusions: Set[(String, String)]): Seq[Dependency] = {
|
||||||
|
|
||||||
val (all, notAll) = exclusions.partition{case ("*", "*") => true; case _ => false}
|
val filter = Exclusions(exclusions)
|
||||||
|
|
||||||
val orgWildcards = notAll.collect{case ("*", name) => name }
|
|
||||||
val nameWildcards = notAll.collect{case (org, "*") => org }
|
|
||||||
|
|
||||||
val remaining = notAll.filterNot{case (org, name) => org == "*" || name == "*" }
|
|
||||||
|
|
||||||
dependencies
|
dependencies
|
||||||
.filter(dep =>
|
.filter(dep => filter(dep.module.organization, dep.module.name))
|
||||||
all.isEmpty &&
|
|
||||||
!orgWildcards(dep.module.name) &&
|
|
||||||
!nameWildcards(dep.module.organization) &&
|
|
||||||
!remaining((dep.module.organization, dep.module.name))
|
|
||||||
)
|
|
||||||
.map(dep =>
|
.map(dep =>
|
||||||
dep.copy(exclusions = exclusionsAdd(dep.exclusions, exclusions))
|
dep.copy(exclusions = Exclusions.minimize(dep.exclusions ++ exclusions))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ package coursier
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import utest._
|
import utest._
|
||||||
import core.Resolver.exclusionsAdd
|
|
||||||
|
|
||||||
object ExclusionsTests extends TestSuite {
|
object ExclusionsTests extends TestSuite {
|
||||||
|
|
||||||
|
def exclusionsAdd(e1: Set[(String, String)], e2: Set[(String, String)]) =
|
||||||
|
core.Exclusions.minimize(e1 ++ e2)
|
||||||
|
|
||||||
val tests = TestSuite {
|
val tests = TestSuite {
|
||||||
val e1 = Set(("org1", "name1"))
|
val e1 = Set(("org1", "name1"))
|
||||||
val e2 = Set(("org2", "name2"))
|
val e2 = Set(("org2", "name2"))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue