Override scala organization and version transitively at the Ivy level. Fixes #2286.

This commit is contained in:
Miles Sabin 2016-06-02 12:05:01 +01:00
parent bb332fae59
commit e98b2363c2
16 changed files with 327 additions and 40 deletions

View File

@ -7,9 +7,10 @@ import java.util.Collections.emptyMap
import scala.collection.mutable.HashSet
import org.apache.ivy.core.module.descriptor.{ DefaultExcludeRule, ExcludeRule }
import org.apache.ivy.core.module.descriptor.{ DependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor, OverrideDependencyDescriptorMediator }
import org.apache.ivy.core.module.descriptor.{ DefaultDependencyDescriptor, DependencyDescriptor, DependencyDescriptorMediator, DefaultModuleDescriptor, ModuleDescriptor, OverrideDependencyDescriptorMediator }
import org.apache.ivy.core.module.id.{ ArtifactId, ModuleId, ModuleRevisionId }
import org.apache.ivy.plugins.matcher.ExactPatternMatcher
import org.apache.ivy.plugins.namespace.NamespaceTransformer
object ScalaArtifacts {
import xsbti.ArtifactInfo._
@ -17,6 +18,8 @@ object ScalaArtifacts {
val LibraryID = ScalaLibraryID
val CompilerID = ScalaCompilerID
val ReflectID = "scala-reflect"
val ActorsID = "scala-actors"
val ScalapID = "scalap"
val DottyIDPrefix = "dotty"
def dottyID(binaryVersion: String): String = s"${DottyIDPrefix}_${binaryVersion}"
@ -47,17 +50,41 @@ private object IvyScala {
/** Performs checks/adds filters on Scala dependencies (if enabled in IvyScala). */
def checkModule(module: DefaultModuleDescriptor, conf: String, log: Logger)(check: IvyScala): Unit = {
if (check.checkExplicit)
checkDependencies(module, check.scalaBinaryVersion, check.configurations, log)
checkDependencies(module, check.scalaOrganization, check.scalaBinaryVersion, check.configurations, log)
if (check.filterImplicit)
excludeScalaJars(module, check.configurations)
if (check.overrideScalaVersion)
overrideScalaVersion(module, check.scalaFullVersion)
overrideScalaVersion(module, check.scalaOrganization, check.scalaFullVersion)
}
def overrideScalaVersion(module: DefaultModuleDescriptor, version: String): Unit = {
overrideVersion(module, Organization, LibraryID, version)
overrideVersion(module, Organization, CompilerID, version)
overrideVersion(module, Organization, ReflectID, version)
class OverrideScalaMediator(scalaOrganization: String, scalaVersion: String) extends DependencyDescriptorMediator {
def mediate(dd: DependencyDescriptor): DependencyDescriptor = {
val transformer =
new NamespaceTransformer {
def transform(mrid: ModuleRevisionId): ModuleRevisionId = {
if (mrid == null) mrid
else
mrid.getName match {
case name @ (CompilerID | LibraryID | ReflectID | ActorsID | ScalapID) =>
ModuleRevisionId.newInstance(scalaOrganization, name, mrid.getBranch, scalaVersion, mrid.getQualifiedExtraAttributes)
case _ => mrid
}
}
def isIdentity: Boolean = false
}
DefaultDependencyDescriptor.transformInstance(dd, transformer, false)
}
}
def overrideScalaVersion(module: DefaultModuleDescriptor, organization: String, version: String): Unit = {
val mediator = new OverrideScalaMediator(organization, version)
module.addDependencyDescriptorMediator(new ModuleId(Organization, "*"), ExactPatternMatcher.INSTANCE, mediator)
if (organization != Organization)
module.addDependencyDescriptorMediator(new ModuleId(organization, "*"), ExactPatternMatcher.INSTANCE, mediator)
}
def overrideVersion(module: DefaultModuleDescriptor, org: String, name: String, version: String): Unit = {
val id = new ModuleId(org, name)
val over = new OverrideDependencyDescriptorMediator(null, version)
@ -68,13 +95,13 @@ private object IvyScala {
* Checks the immediate dependencies of module for dependencies on scala jars and verifies that the version on the
* dependencies matches scalaVersion.
*/
private def checkDependencies(module: ModuleDescriptor, scalaBinaryVersion: String, configurations: Iterable[Configuration], log: Logger): Unit = {
private def checkDependencies(module: ModuleDescriptor, scalaOrganization: String, scalaBinaryVersion: String, configurations: Iterable[Configuration], log: Logger): Unit = {
val configSet = if (configurations.isEmpty) (c: String) => true else configurationSet(configurations)
def binaryScalaWarning(dep: DependencyDescriptor): Option[String] =
{
val id = dep.getDependencyRevisionId
val depBinaryVersion = CrossVersion.binaryScalaVersion(id.getRevision)
def isScalaLangOrg = id.getOrganisation == Organization
def isScalaLangOrg = id.getOrganisation == scalaOrganization
def isNotScalaActorsMigration = !(id.getName startsWith "scala-actors-migration") // Exception to the rule: sbt/sbt#1818
def isNotScalaPickling = !(id.getName startsWith "scala-pickling") // Exception to the rule: sbt/sbt#1899
def hasBinVerMismatch = depBinaryVersion != scalaBinaryVersion

View File

@ -16,7 +16,7 @@ trait BaseIvySpecification extends Specification {
def configurations = Seq(Compile, Test, Runtime)
def module(moduleId: ModuleID, deps: Seq[ModuleID], scalaFullVersion: Option[String],
uo: UpdateOptions = UpdateOptions()): IvySbt#Module = {
uo: UpdateOptions = UpdateOptions(), overrideScalaVersion: Boolean = true): IvySbt#Module = {
val ivyScala = scalaFullVersion map { fv =>
new IvyScala(
scalaFullVersion = fv,
@ -24,7 +24,7 @@ trait BaseIvySpecification extends Specification {
configurations = Nil,
checkExplicit = true,
filterImplicit = false,
overrideScalaVersion = false)
overrideScalaVersion = overrideScalaVersion)
}
val moduleSetting: ModuleSettings = InlineConfiguration(

View File

@ -7,12 +7,16 @@ class EvictionWarningSpec extends BaseIvySpecification {
This is a specification to check the eviction warnings
Eviction of scala-library whose scalaVersion should
Eviction of non-overridden scala-library whose scalaVersion should
be detected $scalaVersionWarn1
not be detected if it's diabled $scalaVersionWarn2
not be detected if it's disabled $scalaVersionWarn2
print out message about the eviction $scalaVersionWarn3
print out message about the eviction with callers $scalaVersionWarn4
Non-eviction of overridden scala-library whose scalaVersion should
not be detected if it's enabled $scalaVersionWarn5
not be detected if it's disabled $scalaVersionWarn6
Including two (suspect) binary incompatible Java libraries to
direct dependencies should
be detected as eviction $javaLibWarn1
@ -69,19 +73,19 @@ class EvictionWarningSpec extends BaseIvySpecification {
def scalaVersionDeps = Seq(scala2102, akkaActor230)
def scalaVersionWarn1 = {
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"), overrideScalaVersion = false)
val report = ivyUpdate(m)
EvictionWarning(m, defaultOptions, report, log).scalaEvictions must have size (1)
}
def scalaVersionWarn2 = {
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"), overrideScalaVersion = false)
val report = ivyUpdate(m)
EvictionWarning(m, defaultOptions.withWarnScalaVersionEviction(false), report, log).scalaEvictions must have size (0)
}
def scalaVersionWarn3 = {
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"), overrideScalaVersion = false)
val report = ivyUpdate(m)
EvictionWarning(m, defaultOptions, report, log).lines must_==
List("Scala version was updated by one of library dependencies:",
@ -92,7 +96,7 @@ class EvictionWarningSpec extends BaseIvySpecification {
}
def scalaVersionWarn4 = {
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"), overrideScalaVersion = false)
val report = ivyUpdate(m)
EvictionWarning(m, defaultOptions.withShowCallers(true), report, log).lines must_==
List("Scala version was updated by one of library dependencies:",
@ -101,6 +105,18 @@ class EvictionWarningSpec extends BaseIvySpecification {
"\tivyScala := ivyScala.value map { _.copy(overrideScalaVersion = true) }")
}
def scalaVersionWarn5 = {
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
val report = ivyUpdate(m)
EvictionWarning(m, defaultOptions, report, log).scalaEvictions must have size (0)
}
def scalaVersionWarn6 = {
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
val report = ivyUpdate(m)
EvictionWarning(m, defaultOptions.withWarnScalaVersionEviction(false), report, log).scalaEvictions must have size (0)
}
def javaLibDirectDeps = Seq(commonsIo14, commonsIo24)
def javaLibWarn1 = {

View File

@ -0,0 +1,75 @@
package sbt
import java.io.File
import org.apache.ivy.core.module.id.{ ModuleId, ModuleRevisionId }
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
import org.specs2._
import mutable.Specification
import cross.CrossVersionUtil
import IvyScala.OverrideScalaMediator
import ScalaArtifacts._
object ScalaOverrideTest extends Specification {
val OtherOrgID = "other.org"
def check(org0: String, version0: String)(org1: String, name1: String, version1: String) = {
val osm = new OverrideScalaMediator(org0, version0)
val mrid = ModuleRevisionId.newInstance(org1, name1, version1)
val dd = new DefaultDependencyDescriptor(mrid, false)
val res = osm.mediate(dd)
res.getDependencyRevisionId must_== ModuleRevisionId.newInstance(org0, name1, version0)
}
"OverrideScalaMediator" should {
"Override compiler version" in {
check(Organization, "2.11.8")(Organization, CompilerID, "2.11.9")
}
"Override library version" in {
check(Organization, "2.11.8")(Organization, LibraryID, "2.11.8")
}
"Override reflect version" in {
check(Organization, "2.11.8")(Organization, ReflectID, "2.11.7")
}
"Override actors version" in {
check(Organization, "2.11.8")(Organization, ActorsID, "2.11.6")
}
"Override scalap version" in {
check(Organization, "2.11.8")(Organization, ScalapID, "2.11.5")
}
"Override default compiler organization" in {
check(OtherOrgID, "2.11.8")(Organization, CompilerID, "2.11.9")
}
"Override default library organization" in {
check(OtherOrgID, "2.11.8")(Organization, LibraryID, "2.11.8")
}
"Override default reflect organization" in {
check(OtherOrgID, "2.11.8")(Organization, ReflectID, "2.11.7")
}
"Override default actors organization" in {
check(OtherOrgID, "2.11.8")(Organization, ActorsID, "2.11.6")
}
"Override default scalap organization" in {
check(OtherOrgID, "2.11.8")(Organization, ScalapID, "2.11.5")
}
"Override custom compiler organization" in {
check(Organization, "2.11.8")(OtherOrgID, CompilerID, "2.11.9")
}
"Override custom library organization" in {
check(Organization, "2.11.8")(OtherOrgID, LibraryID, "2.11.8")
}
"Override custom reflect organization" in {
check(Organization, "2.11.8")(OtherOrgID, ReflectID, "2.11.7")
}
"Override custom actors organization" in {
check(Organization, "2.11.8")(OtherOrgID, ActorsID, "2.11.6")
}
"Override custom scalap organization" in {
check(Organization, "2.11.8")(OtherOrgID, ScalapID, "2.11.5")
}
}
}

View File

@ -1183,7 +1183,7 @@ object Classpaths {
projectDependencies.value ++ libraryDependencies.value
},
ivyScala <<= ivyScala or (scalaHome, scalaVersion in update, scalaBinaryVersion in update, scalaOrganization, sbtPlugin) { (sh, fv, bv, so, plugin) =>
Some(new IvyScala(fv, bv, Nil, filterImplicit = false, checkExplicit = true, overrideScalaVersion = plugin, scalaOrganization = so))
Some(new IvyScala(fv, bv, Nil, filterImplicit = false, checkExplicit = true, overrideScalaVersion = true, scalaOrganization = so))
},
artifactPath in makePom <<= artifactPathSetting(artifact in makePom),
publishArtifact in makePom := publishMavenStyle.value && publishArtifact.value,

View File

@ -0,0 +1,17 @@
[milessabin]: https://github.com/milessabin
[2286]: https://github.com/sbt/sbt/issues/2286
[2634]: https://github.com/sbt/sbt/pull/2634
### Fixes with compatibility implications
- By default the Scala toolchain artefacts are now transitively resolved using the provided `scalaVersion` and
`scalaOrganization`. Previously a user specified `scalaOrganization` would not have affected transitive
dependencies on, eg. `scala-reflect`. An Ivy-level mechanism is used for this purpose, and as a consequence
the overriding happens early in the resolution process which might improve resolution times, and as a side
benefit fixes [#2286][2286]. The old behaviour can be restored by adding
`ivyScala := { ivyScala.value map {_.copy(overrideScalaVersion = sbtPlugin.value)} }`
to your build. [#2286][2286]/[#2634][2634] by [@milessabin][milessabin]
### Improvements
### Bug fixes

View File

@ -1,27 +1,28 @@
import sbt._
import Keys._
import sbt._
import Keys._
object ExcludeScala extends Build
{
lazy val root = Project("root", file(".")) settings(
libraryDependencies <++= baseDirectory(dependencies),
scalaVersion := "2.9.2",
autoScalaLibrary <<= baseDirectory(base => !(base / "noscala").exists ),
scalaOverride <<= check("scala.App")
)
def check(className: String): Def.Initialize[Task[Unit]] = fullClasspath in Compile map { cp =>
val existing = cp.files.filter(_.getName contains "scala-library")
println("Full classpath: " + cp.mkString("\n\t", "\n\t", ""))
println("scala-library.jar: " + existing.mkString("\n\t", "\n\t", ""))
val loader = classpath.ClasspathUtilities.toLoader(existing)
Class.forName(className, false, loader)
}
lazy val root = Project("root", file(".")) settings(
libraryDependencies <++= baseDirectory(dependencies),
scalaVersion := "2.9.2",
ivyScala := { ivyScala.value map {_.copy(overrideScalaVersion = sbtPlugin.value)} },
autoScalaLibrary <<= baseDirectory(base => !(base / "noscala").exists ),
scalaOverride <<= check("scala.App")
)
def check(className: String): Def.Initialize[Task[Unit]] = fullClasspath in Compile map { cp =>
val existing = cp.files.filter(_.getName contains "scala-library")
println("Full classpath: " + cp.mkString("\n\t", "\n\t", ""))
println("scala-library.jar: " + existing.mkString("\n\t", "\n\t", ""))
val loader = classpath.ClasspathUtilities.toLoader(existing)
Class.forName(className, false, loader)
}
lazy val scalaOverride = taskKey[Unit]("Check that the proper version of Scala is on the classpath.")
lazy val scalaOverride = taskKey[Unit]("Check that the proper version of Scala is on the classpath.")
def dependencies(base: File) =
if( ( base / "stm").exists )
("org.scala-tools" % "scala-stm_2.8.2" % "0.6") :: Nil
else
Nil
}
def dependencies(base: File) =
if( ( base / "stm").exists )
("org.scala-tools" % "scala-stm_2.8.2" % "0.6") :: Nil
else
Nil
}

View File

@ -0,0 +1,54 @@
organization := "org.dummy"
scalaOrganization := "org.other"
scalaVersion := "2.11.8"
resolvers += Resolver.file("buggy", (baseDirectory in LocalRootProject).value / "repo")(
Patterns(
ivyPatterns = Seq("[organization]/[module]/[revision]/ivy.xml"),
artifactPatterns = Seq("[organization]/[module]/[revision]/dummy.jar"),
isMavenCompatible = false,
descriptorOptional = true,
skipConsistencyCheck = true
)
)
libraryDependencies += "org.typelevel" %% "cats" % "0.6.0"
val checkDependencies = taskKey[Unit]("Checks that dependcies are correct.")
checkDependencies := {
val expected: Set[ModuleID] = Set(
"com.github.mpilquist" % "simulacrum_2.11" % "0.7.0",
"jline" % "jline" % "2.12.1",
"org.other" % "scala-compiler" % "2.11.8",
"org.other" % "scala-library" % "2.11.8",
"org.other" % "scala-reflect" % "2.11.8",
"org.scala-lang.modules" % "scala-parser-combinators_2.11" % "1.0.4",
"org.scala-lang.modules" % "scala-xml_2.11" % "1.0.5",
"org.scala-sbt" % "test-interface" % "1.0",
"org.scalacheck" % "scalacheck_2.11" % "1.12.5",
"org.typelevel" % "catalysts-macros_2.11" % "0.0.2",
"org.typelevel" % "catalysts-platform_2.11" % "0.0.2",
"org.typelevel" % "cats-core_2.11" % "0.6.0",
"org.typelevel" % "cats-free_2.11" % "0.6.0",
"org.typelevel" % "cats-kernel-laws_2.11" % "0.6.0",
"org.typelevel" % "cats-kernel_2.11" % "0.6.0",
"org.typelevel" % "cats-laws_2.11" % "0.6.0",
"org.typelevel" % "cats-macros_2.11" % "0.6.0",
"org.typelevel" % "cats_2.11" % "0.6.0",
"org.typelevel" % "discipline_2.11" % "0.4",
"org.typelevel" % "machinist_2.11" % "0.4.1",
"org.typelevel" % "macro-compat_2.11" % "1.1.0"
)
val resolved: Set[ModuleID] =
(for {
c <- update.value.configurations
m <- c.modules
if !m.evicted
} yield m.module.copy(extraAttributes = Map.empty)).toSet
assert(resolved == expected)
}

View File

@ -0,0 +1,37 @@
<?xml version='1.0' encoding='ISO-8859-1'?>
<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
<info publication="20160520164059" status="release" revision="2.11.8" module="scala-compiler" organisation="org.other">
<license url="http://www.scala-lang.org/license.html" name="BSD 3-Clause"/>
<description homepage="http://www.scala-lang.org">
Scala Compiler
</description>
</info>
<configurations>
<conf description="" visibility="public" name="compile"/>
<conf extends="compile" description="" visibility="public" name="runtime"/>
<conf extends="runtime" description="" visibility="public" name="test"/>
<conf description="" visibility="public" name="provided"/>
<conf description="" visibility="public" name="optional"/>
<conf description="" visibility="public" name="sources"/>
<conf description="" visibility="public" name="docs"/>
<conf description="" visibility="public" name="pom"/>
<conf extends="runtime" description="Default" visibility="public" name="default"/>
</configurations>
<publications>
<artifact conf="pom" ext="pom" type="pom" name="scala-compiler"/>
<artifact conf="compile" ext="jar" type="jar" name="scala-compiler"/>
<artifact e:classifier="sources" conf="sources" ext="jar" type="src" name="scala-compiler"/>
<artifact e:classifier="javadoc" conf="docs" ext="jar" type="doc" name="scala-compiler"/>
</publications>
<dependencies>
<dependency conf="compile-&gt;default(compile)" rev="2.11.8" name="scala-library" org="org.other"/>
<dependency conf="compile-&gt;default(compile)" rev="2.11.8" name="scala-reflect" org="org.other"/>
<dependency conf="compile-&gt;default(compile)" rev="1.0.5" name="scala-xml_2.11" org="org.scala-lang.modules">
<exclude matcher="exact" conf="" ext="*" type="*" name="*" module="*" org="org.scala-lang"/>
</dependency>
<dependency conf="compile-&gt;default(compile)" rev="1.0.4" name="scala-parser-combinators_2.11" org="org.scala-lang.modules">
<exclude matcher="exact" conf="" ext="*" type="*" name="*" module="*" org="org.scala-lang"/>
</dependency>
<dependency conf="optional-&gt;default(compile)" rev="2.12.1" name="jline" org="jline"/>
</dependencies>
</ivy-module>

View File

@ -0,0 +1,28 @@
<?xml version='1.0' encoding='ISO-8859-1'?>
<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
<info publication="20160520164058" status="release" revision="2.11.8" module="scala-library" organisation="org.other">
<license url="http://www.scala-lang.org/license.html" name="BSD 3-Clause"/>
<description homepage="http://www.scala-lang.org">
Scala Standard Library
</description>
</info>
<configurations>
<conf description="" visibility="public" name="compile"/>
<conf extends="compile" description="" visibility="public" name="runtime"/>
<conf extends="runtime" description="" visibility="public" name="test"/>
<conf description="" visibility="public" name="provided"/>
<conf description="" visibility="public" name="optional"/>
<conf description="" visibility="public" name="sources"/>
<conf description="" visibility="public" name="docs"/>
<conf description="" visibility="public" name="pom"/>
<conf extends="runtime" description="Default" visibility="public" name="default"/>
</configurations>
<publications>
<artifact conf="pom" ext="pom" type="pom" name="scala-library"/>
<artifact conf="compile" ext="jar" type="jar" name="scala-library"/>
<artifact e:classifier="sources" conf="sources" ext="jar" type="src" name="scala-library"/>
<artifact e:classifier="javadoc" conf="docs" ext="jar" type="doc" name="scala-library"/>
</publications>
<dependencies>
</dependencies>
</ivy-module>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
<info organisation="org.other" module="scala-reflect" revision="2.11.8" status="release" publication="20160520164058">
<license name="BSD 3-Clause" url="http://www.scala-lang.org/license.html"/>
<description homepage="http://www.scala-lang.org">
Scala Reflection Library
</description>
</info>
<configurations>
<conf name="compile" visibility="public" description=""/>
<conf name="runtime" visibility="public" description="" extends="compile"/>
<conf name="test" visibility="public" description="" extends="runtime"/>
<conf name="provided" visibility="public" description=""/>
<conf name="optional" visibility="public" description=""/>
<conf name="sources" visibility="public" description=""/>
<conf name="docs" visibility="public" description=""/>
<conf name="pom" visibility="public" description=""/>
<conf name="default" visibility="public" description="Default" extends="runtime"/>
</configurations>
<publications>
<artifact name="scala-reflect" type="pom" ext="pom" conf="pom"/>
<artifact name="scala-reflect" type="jar" ext="jar" conf="compile"/>
<artifact name="scala-reflect" type="src" ext="jar" conf="sources" e:classifier="sources"/>
<artifact name="scala-reflect" type="doc" ext="jar" conf="docs" e:classifier="javadoc"/>
</publications>
<dependencies>
<dependency org="org.other" name="scala-library" rev="2.11.8" conf="compile->default(compile)"/>
</dependencies>
</ivy-module>

View File

@ -0,0 +1 @@
> checkDependencies

View File

@ -27,3 +27,5 @@ libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.3" % "test"
scalaVersion := "2.11.0"
ivyScala := ivyScala.value map {_.copy(overrideScalaVersion = sbtPlugin.value)}