Rework profile activation

- don't activate profiles activated by default if some user activated profiles are provided, and
- accept OS / JDK conditions in activation
This commit is contained in:
Alexandre Archambault 2016-11-07 11:22:49 +01:00
parent 7de95c927d
commit bf097fa018
No known key found for this signature in database
GPG Key ID: 14640A6839C263A9
12 changed files with 567 additions and 54 deletions

View File

@ -130,6 +130,18 @@ lazy val core = crossProject
import com.typesafe.tools.mima.core._
Seq(
// Since 1.0.0-M15
// reworked profile activation
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Resolution.copy"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Resolution.profileActivation"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Resolution.copyWithCache"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Resolution.this"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Activation.copy"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Activation.this"),
ProblemFilters.exclude[MissingTypesProblem]("coursier.core.Activation$"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Activation.apply"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Resolution.profiles"),
ProblemFilters.exclude[DirectMissingMethodProblem]("coursier.core.Resolution.apply"),
// Since 1.0.0-M13
// reworked VersionConstraint
ProblemFilters.exclude[MissingClassProblem]("coursier.core.VersionConstraint$Interval"),

View File

@ -285,7 +285,9 @@ class Helper(
dependencies.toSet,
forceVersions = forceVersions,
filter = Some(dep => keepOptional || !dep.optional),
profileActivation = Some(core.Resolution.userProfileActivation(userEnabledProfiles))
userActivations =
if (userEnabledProfiles.isEmpty) None
else Some(userEnabledProfiles.iterator.map(_ -> true).toMap)
)
val loggerFallbackMode =

View File

@ -0,0 +1,131 @@
package coursier.core
import scalaz.{-\/, \/, \/-}
// Maven-specific
final case class Activation(
properties: Seq[(String, Option[String])],
os: Activation.Os,
jdk: Option[VersionInterval \/ Seq[Version]]
) {
def isEmpty: Boolean = properties.isEmpty && os.isEmpty && jdk.isEmpty
def isActive(
currentProperties: Map[String, String],
osInfo: Activation.Os,
jdkVersion: Option[Version]
): Boolean = {
def fromProperties = properties.forall {
case (name, valueOpt) =>
if (name.startsWith("!"))
currentProperties.get(name.drop(1)).isEmpty
else
currentProperties.get(name).exists { v =>
valueOpt.forall { reqValue =>
if (reqValue.startsWith("!"))
v != reqValue.drop(1)
else
v == reqValue
}
}
}
def fromOs = os.isActive(osInfo)
def fromJdk = jdk.forall {
case -\/(itv) =>
jdkVersion.exists(itv.contains)
case \/-(versions) =>
jdkVersion.exists(versions.contains)
}
!isEmpty && fromProperties && fromOs && fromJdk
}
}
object Activation {
case class Os(
arch: Option[String],
families: Set[String],
name: Option[String],
version: Option[String] // FIXME Could this be an interval?
) {
def isEmpty: Boolean =
arch.isEmpty && families.isEmpty && name.isEmpty && version.isEmpty
def isActive(osInfo: Os): Boolean =
arch.forall(osInfo.arch.toSeq.contains) &&
families.forall { f =>
if (Os.knownFamilies(f))
osInfo.families.contains(f)
else
osInfo.name.exists(_.contains(f))
} &&
name.forall(osInfo.name.toSeq.contains) &&
version.forall(osInfo.version.toSeq.contains)
}
object Os {
val empty = Os(None, Set(), None, None)
// below logic adapted from https://github.com/sonatype/plexus-utils/blob/f2beca21c75084986b49b3ab7b5f0f988021dcea/src/main/java/org/codehaus/plexus/util/Os.java
// brought in https://github.com/alexarchambault/coursier/issues/341 by @eboto
private val standardFamilies = Set(
"windows",
"os/2",
"netware",
"mac",
"os/400",
"openvms"
)
private[Os] val knownFamilies = standardFamilies ++ Seq(
"dos",
"tandem",
"unix",
"win9x",
"z/os"
)
def families(name: String, pathSep: String): Set[String] = {
var families = standardFamilies.filter(f => name.indexOf(f) >= 0)
if (pathSep == ";" && name.indexOf("netware") < 0)
families += "dos"
if (name.indexOf("nonstop_kernel") >= 0)
families += "tandem"
if (pathSep == ":" && name.indexOf("openvms") < 0 && (name.indexOf("mac") < 0 || name.endsWith("x")))
families += "unix"
if (name.indexOf("windows") >= 0 && (name.indexOf("95") >= 0 || name.indexOf("98") >= 0 || name.indexOf("me") >= 0 || name.indexOf("ce") >= 0))
families += "win9x"
if (name.indexOf("z/os") >= 0 || name.indexOf("os/390") >= 0)
families += "z/os"
families
}
def fromProperties(properties: Map[String, String]): Os = {
val name = properties.get("os.name").map(_.toLowerCase)
Os(
properties.get("os.arch").map(_.toLowerCase),
(for (n <- name; sep <- properties.get("path.separator"))
yield families(n, sep)).getOrElse(Set()),
name,
properties.get("os.version").map(_.toLowerCase)
)
}
}
val empty = Activation(Nil, Os.empty, None)
}

View File

@ -127,9 +127,6 @@ object Info {
val empty = Info("", "", Nil, Nil, None)
}
// Maven-specific
final case class Activation(properties: Seq[(String, Option[String])])
// Maven-specific
final case class Profile(
id: String,

View File

@ -11,25 +11,53 @@ object Resolution {
type ModuleVersion = (Module, String)
def profileIsActive(
profile: Profile,
properties: Map[String, String],
osInfo: Activation.Os,
jdkVersion: Option[Version],
userActivations: Option[Map[String, Boolean]]
): Boolean = {
val fromUserOrDefault = userActivations match {
case Some(activations) =>
activations.get(profile.id)
case None =>
if (profile.activeByDefault.toSeq.contains(true))
Some(true)
else
None
}
def fromActivation = profile.activation.isActive(properties, osInfo, jdkVersion)
val res = fromUserOrDefault.getOrElse(fromActivation)
// println(s"Profile\n$profile\n$res\n")
res
}
/**
* Get the active profiles of `project`, using the current properties `properties`,
* and `profileActivation` stating if a profile is active.
* and `profileActivations` stating if a profile is active.
*/
def profiles(
project: Project,
properties: Map[String, String],
profileActivation: (String, Activation, Map[String, String]) => Boolean
): Seq[Profile] = {
val activated = project.profiles
.filter(p => profileActivation(p.id, p.activation, properties))
def default = project.profiles
.filter(_.activeByDefault.toSeq.contains(true))
if (activated.isEmpty) default
else activated
}
osInfo: Activation.Os,
jdkVersion: Option[Version],
userActivations: Option[Map[String, Boolean]]
): Seq[Profile] =
project.profiles.filter { profile =>
profileIsActive(
profile,
properties,
osInfo,
jdkVersion,
userActivations
)
}
object DepMgmt {
type Key = (String, String, String)
@ -500,7 +528,9 @@ final case class Resolution(
errorCache: Map[Resolution.ModuleVersion, Seq[String]],
finalDependenciesCache: Map[Dependency, Seq[Dependency]],
filter: Option[Dependency => Boolean],
profileActivation: Option[(String, Activation, Map[String, String]) => Boolean]
osInfo: Activation.Os,
jdkVersion: Option[Version],
userActivations: Option[Map[String, Boolean]]
) {
def copyWithCache(
@ -511,7 +541,9 @@ final case class Resolution(
projectCache: Map[Resolution.ModuleVersion, (Artifact.Source, Project)] = projectCache,
errorCache: Map[Resolution.ModuleVersion, Seq[String]] = errorCache,
filter: Option[Dependency => Boolean] = filter,
profileActivation: Option[(String, Activation, Map[String, String]) => Boolean] = profileActivation
osInfo: Activation.Os = osInfo,
jdkVersion: Option[Version] = jdkVersion,
userActivations: Option[Map[String, Boolean]] = userActivations
): Resolution =
copy(
rootDependencies,
@ -522,7 +554,9 @@ final case class Resolution(
errorCache,
finalDependenciesCache ++ finalDependenciesCache0.asScala,
filter,
profileActivation
osInfo,
jdkVersion,
userActivations
)
import Resolution._
@ -741,7 +775,9 @@ final case class Resolution(
profiles(
project,
approxProperties,
profileActivation getOrElse defaultProfileActivation
osInfo,
jdkVersion,
userActivations
).flatMap(p => p.dependencies ++ p.dependencyManagement)
val modules = withProperties(
@ -850,7 +886,9 @@ final case class Resolution(
val profiles0 = profiles(
project,
approxProperties,
profileActivation getOrElse defaultProfileActivation
osInfo,
jdkVersion,
userActivations
)
// 1.2 made from Pom.scala (TODO look at the very details?)

View File

@ -75,7 +75,23 @@ object Pom {
} yield (name, valueOpt)
}
(byDefault, Activation(properties))
val osNodeOpt = node.children.collectFirst { case n if n.label == "os" => n }
val os = Activation.Os(
osNodeOpt.flatMap(n => text(n, "arch", "").toOption),
osNodeOpt.flatMap(n => text(n, "family", "").toOption).toSet,
osNodeOpt.flatMap(n => text(n, "name", "").toOption),
osNodeOpt.flatMap(n => text(n, "version", "").toOption)
)
val jdk = text(node, "jdk", "").toOption.flatMap { s =>
Parse.versionInterval(s).map(-\/(_))
.orElse(Parse.version(s).map(v => \/-(Seq(v))))
}
val activation = Activation(properties, os, jdk)
(byDefault, activation)
}
def profile(node: Node): String \/ Profile = {
@ -85,7 +101,7 @@ object Pom {
val xmlActivationOpt = node.children
.find(_.label == "activation")
val (activeByDefault, activation) = xmlActivationOpt.fold((Option.empty[Boolean], Activation(Nil)))(profileActivation)
val (activeByDefault, activation) = xmlActivationOpt.fold((Option.empty[Boolean], Activation.empty))(profileActivation)
val xmlDeps = node.children
.find(_.label == "dependencies")

View File

@ -1,3 +1,4 @@
import coursier.core.{Activation, Parse, Version}
/**
* Mainly pulls definitions from coursier.core, sometimes with default arguments.
@ -72,7 +73,9 @@ package object coursier {
errorCache: Map[ModuleVersion, Seq[String]] = Map.empty,
finalDependencies: Map[Dependency, Seq[Dependency]] = Map.empty,
filter: Option[Dependency => Boolean] = None,
profileActivation: Option[(String, core.Activation, Map[String, String]) => Boolean] = None
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
): Resolution =
core.Resolution(
rootDependencies,
@ -83,7 +86,9 @@ package object coursier {
errorCache,
finalDependencies,
filter,
profileActivation
osInfo,
jdkVersion,
userActivations
)
}

View File

@ -441,7 +441,11 @@ object Tasks {
dep.copy(exclusions = dep.exclusions ++ exclusions)
}.toSet,
filter = Some(dep => !dep.optional),
profileActivation = Some(core.Resolution.userProfileActivation(userEnabledProfiles)),
userActivations =
if (userEnabledProfiles.isEmpty)
None
else
Some(userEnabledProfiles.iterator.map(_ -> true).toMap),
forceVersions =
// order matters here
userForceVersions ++
@ -677,7 +681,7 @@ object Tasks {
currentProject,
repositories,
userEnabledProfiles,
startRes.copy(filter = None, profileActivation = None),
startRes.copy(filter = None),
sbtClassifiers
),
resolution
@ -910,7 +914,7 @@ object Tasks {
reportsCache.getOrElseUpdate(
ReportCacheKey(
currentProject,
res.copy(filter = None, profileActivation = None),
res.copy(filter = None),
withClassifiers,
sbtClassifiers
),

View File

@ -13,13 +13,9 @@ com.google.guava:guava:14.0.1:compile
com.google.inject:guice:3.0:compile
com.google.protobuf:protobuf-java:2.5.0:compile
com.ning:compress-lzf:1.0.0:compile
com.sun.jersey:jersey-client:1.9:compile
com.sun.jersey:jersey-core:1.9:compile
com.sun.jersey:jersey-grizzly2:1.9:compile
com.sun.jersey:jersey-json:1.9:compile
com.sun.jersey:jersey-server:1.9:compile
com.sun.jersey.contribs:jersey-guice:1.9:compile
com.sun.jersey.jersey-test-framework:jersey-test-framework-core:1.9:compile
com.sun.jersey.jersey-test-framework:jersey-test-framework-grizzly2:1.9:compile
com.sun.xml.bind:jaxb-impl:2.2.3-1:compile
com.thoughtworks.paranamer:paranamer:2.6:compile
@ -46,7 +42,6 @@ io.netty:netty:3.8.0.Final:compile
io.netty:netty-all:4.0.23.Final:compile
javax.activation:activation:1.1:compile
javax.inject:javax.inject:1:compile
javax.servlet:javax.servlet-api:3.0.1:compile
javax.xml.bind:jaxb-api:2.2.2:compile
jline:jline:0.9.94:compile
log4j:log4j:1.2.17:compile
@ -88,14 +83,6 @@ org.codehaus.jackson:jackson-mapper-asl:1.8.8:compile
org.codehaus.jackson:jackson-xc:1.8.3:compile
org.codehaus.jettison:jettison:1.1:compile
org.eclipse.jetty.orbit:javax.servlet:3.0.0.v201112011016:compile
org.glassfish:javax.servlet:3.1:compile
org.glassfish.external:management-api:3.0.0-b012:compile
org.glassfish.gmbal:gmbal-api-only:3.0.0-b023:compile
org.glassfish.grizzly:grizzly-framework:2.1.2:compile
org.glassfish.grizzly:grizzly-http:2.1.2:compile
org.glassfish.grizzly:grizzly-http-server:2.1.2:compile
org.glassfish.grizzly:grizzly-http-servlet:2.1.2:compile
org.glassfish.grizzly:grizzly-rcm:2.1.2:compile
org.json4s:json4s-ast_2.11:3.2.10:compile
org.json4s:json4s-core_2.11:3.2.10:compile
org.json4s:json4s-jackson_2.11:3.2.10:compile

View File

@ -0,0 +1,325 @@
package coursier.test
import coursier.core.{Activation, Parse}
import coursier.core.Activation.Os
import utest._
import scalaz.{-\/, \/-}
object ActivationTests extends TestSuite {
def parseVersion(s: String) = Parse.version(s).getOrElse(???)
def parseVersionInterval(s: String) = Parse.versionInterval(s).getOrElse(???)
val macOs = Os(
Some("x86_64"),
Set("mac", "unix"),
Some("mac os x"),
Some("10.12")
)
val jdkVersion = parseVersion("1.8.0_112")
// missing:
// - condition on OS or JDK, but no OS or JDK info provided (-> no match)
// - negated OS infos (starting with "!") - not implemented yet
val tests = TestSuite {
'OS - {
'fromProperties - {
'MacOSX - {
val props = Map(
"os.arch" -> "x86_64",
"os.name" -> "Mac OS X",
"os.version" -> "10.12",
"path.separator" -> ":"
)
val expectedOs = Os(
Some("x86_64"),
Set("mac", "unix"),
Some("mac os x"),
Some("10.12")
)
val os = Os.fromProperties(props)
assert(os == expectedOs)
}
'linuxPi - {
val props = Map(
"os.arch" -> "arm",
"os.name" -> "Linux",
"os.version" -> "4.1.13-v7+",
"path.separator" -> ":"
)
val expectedOs = Os(
Some("arm"),
Set("unix"),
Some("linux"),
Some("4.1.13-v7+")
)
val os = Os.fromProperties(props)
assert(os == expectedOs)
}
}
'active - {
'arch - {
val activation = Os(Some("x86_64"), Set(), None, None)
val isActive = activation.isActive(macOs)
assert(isActive)
}
'wrongArch - {
val activation = Os(Some("arm"), Set(), None, None)
val isActive = activation.isActive(macOs)
assert(!isActive)
}
'family - {
val activation = Os(None, Set("mac"), None, None)
val isActive = activation.isActive(macOs)
assert(isActive)
}
'wrongFamily - {
val activation = Os(None, Set("windows"), None, None)
val isActive = activation.isActive(macOs)
assert(!isActive)
}
'name - {
val activation = Os(None, Set(), Some("mac os x"), None)
val isActive = activation.isActive(macOs)
assert(isActive)
}
'wrongName - {
val activation = Os(None, Set(), Some("linux"), None)
val isActive = activation.isActive(macOs)
assert(!isActive)
}
'version - {
val activation = Os(None, Set(), None, Some("10.12"))
val isActive = activation.isActive(macOs)
assert(isActive)
}
'wrongVersion - {
val activation = Os(None, Set(), None, Some("10.11"))
val isActive = activation.isActive(macOs)
assert(!isActive)
}
}
}
'properties - {
val activation = Activation.empty.copy(
properties = Seq(
"required" -> None,
"requiredWithValue" -> Some("foo"),
"requiredWithNegValue" -> Some("!bar")
)
)
'match - {
val isActive = activation.isActive(
Map(
"required" -> "a",
"requiredWithValue" -> "foo",
"requiredWithNegValue" -> "baz"
),
Os.empty,
None
)
assert(isActive)
}
'noMatch - {
* - {
val isActive = activation.isActive(
Map(
"requiredWithValue" -> "foo",
"requiredWithNegValue" -> "baz"
),
Os.empty,
None
)
assert(!isActive)
}
* - {
val isActive = activation.isActive(
Map(
"required" -> "a",
"requiredWithValue" -> "fooz",
"requiredWithNegValue" -> "baz"
),
Os.empty,
None
)
assert(!isActive)
}
* - {
val isActive = activation.isActive(
Map(
"required" -> "a",
"requiredWithValue" -> "foo",
"requiredWithNegValue" -> "bar"
),
Os.empty,
None
)
assert(!isActive)
}
}
}
'jdkVersion - {
'match - {
'exactVersion - {
val activation = Activation(
Nil,
Os.empty,
Some(\/-(Seq(parseVersion("1.8.0_112"))))
)
val isActive = activation.isActive(Map(), Os.empty, Some(jdkVersion))
assert(isActive)
}
'exactVersionSeveral - {
val activation = Activation(
Nil,
Os.empty,
Some(\/-(Seq(parseVersion("1.8.0_102"), parseVersion("1.8.0_112"))))
)
val isActive = activation.isActive(Map(), Os.empty, Some(jdkVersion))
assert(isActive)
}
'wrongExactVersion - {
val activation = Activation(
Nil,
Os.empty,
Some(\/-(Seq(parseVersion("1.8.0_102"))))
)
val isActive = activation.isActive(Map(), Os.empty, Some(jdkVersion))
assert(!isActive)
}
'wrongExactVersionSeveral - {
val activation = Activation(
Nil,
Os.empty,
Some(\/-(Seq(parseVersion("1.8.0_92"), parseVersion("1.8.0_102"))))
)
val isActive = activation.isActive(Map(), Os.empty, Some(jdkVersion))
assert(!isActive)
}
'versionInterval - {
val activation = Activation(
Nil,
Os.empty,
Some(-\/(parseVersionInterval("[1.8,)")))
)
val isActive = activation.isActive(Map(), Os.empty, Some(jdkVersion))
assert(isActive)
}
'wrongVersionInterval - {
val activation = Activation(
Nil,
Os.empty,
Some(-\/(parseVersionInterval("[1.7,1.8)")))
)
val isActive = activation.isActive(Map(), Os.empty, Some(jdkVersion))
assert(!isActive)
}
}
}
'all - {
val activation = Activation(
Seq(
"required" -> None,
"requiredWithValue" -> Some("foo"),
"requiredWithNegValue" -> Some("!bar")
),
Os(None, Set("mac"), None, None),
Some(-\/(parseVersionInterval("[1.8,)")))
)
'match - {
val isActive = activation.isActive(
Map(
"required" -> "a",
"requiredWithValue" -> "foo",
"requiredWithNegValue" -> "baz"
),
macOs,
Some(jdkVersion)
)
assert(isActive)
}
'noMatch - {
val isActive = activation.isActive(
Map(
"requiredWithValue" -> "foo",
"requiredWithNegValue" -> "baz"
),
macOs,
Some(jdkVersion)
)
assert(!isActive)
}
}
}
}

View File

@ -19,16 +19,14 @@ object CentralTests extends TestSuite {
deps: Set[Dependency],
filter: Option[Dependency => Boolean] = None,
extraRepo: Option[Repository] = None,
profiles: Set[String] = Set.empty
profiles: Option[Set[String]] = None
) = {
val repositories0 = extraRepo.toSeq ++ repositories
Resolution(
deps,
filter = filter,
profileActivation = Some(
core.Resolution.userProfileActivation(profiles)
)
userActivations = profiles.map(_.iterator.map(_ -> true).toMap)
)
.process
.run(repositories0)
@ -40,7 +38,7 @@ object CentralTests extends TestSuite {
version: String,
extraRepo: Option[Repository] = None,
configuration: String = "",
profiles: Set[String] = Set.empty
profiles: Option[Set[String]] = None
) =
async {
val attrPathPart =
@ -157,7 +155,7 @@ object CentralTests extends TestSuite {
'logback - {
async {
val dep = Dependency(Module("ch.qos.logback", "logback-classic"), "1.1.3")
val res = await(resolve(Set(dep))).clearCaches.clearProfileActivation
val res = await(resolve(Set(dep))).clearCaches
val expected = Resolution(
rootDependencies = Set(dep),
@ -173,7 +171,7 @@ object CentralTests extends TestSuite {
'asm - {
async {
val dep = Dependency(Module("org.ow2.asm", "asm-commons"), "5.0.2")
val res = await(resolve(Set(dep))).clearCaches.clearProfileActivation
val res = await(resolve(Set(dep))).clearCaches
val expected = Resolution(
rootDependencies = Set(dep),
@ -190,7 +188,7 @@ object CentralTests extends TestSuite {
async {
val dep = Dependency(Module("joda-time", "joda-time"), "[2.2,2.8]")
val res0 = await(resolve(Set(dep)))
val res = res0.clearCaches.clearProfileActivation
val res = res0.clearCaches
val expected = Resolution(
rootDependencies = Set(dep),
@ -209,7 +207,7 @@ object CentralTests extends TestSuite {
resolutionCheck(
Module("org.apache.spark", "spark-core_2.11"),
"1.3.1",
profiles = Set("hadoop-2.2")
profiles = Some(Set("hadoop-2.2"))
)
}

View File

@ -23,15 +23,13 @@ package object test {
)
def clearFilter: Resolution =
underlying.copy(filter = None)
def clearProfileActivation: Resolution =
underlying.copy(profileActivation = None)
}
object Profile {
type Activation = core.Activation
object Activation {
def apply(properties: Seq[(String, Option[String])] = Nil): Activation =
core.Activation(properties)
core.Activation(properties, coursier.core.Activation.Os.empty, None)
}
def apply(