Refactor exclusions, add partial orders / minifications

This commit is contained in:
Alexandre Archambault 2015-06-25 00:18:47 +01:00
parent e6ec32f33a
commit 54338f7b04
4 changed files with 201 additions and 42 deletions

View File

@ -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
}
}
}

View File

@ -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)(_ ++ _)
}
}

View File

@ -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))
) )
} }

View File

@ -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"))