From 07afc67c7a582ff6680fe958f0e502379325e31d Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 4 Dec 2014 17:35:30 -0500 Subject: [PATCH] Adds excludeDependencies. Fixes #1748 Adds project-level dependency exclusions: excludeDependencies += "org.apache.logging.log4j" excludeDependencies += "com.example" %% "foo" In the first example, all artifacts from the organization `"org.apache.logging.log4j"` are excluded from the managed dependency. In the second example, artifacts with the organization `"com.example"` and the name `"foo"` cross versioned to the current `scalaVersion` are excluded. --- ivy/src/main/scala/sbt/CrossVersion.scala | 7 ++ ivy/src/main/scala/sbt/EvictionWarning.scala | 5 +- ivy/src/main/scala/sbt/Ivy.scala | 32 ++++++--- ivy/src/main/scala/sbt/IvyActions.scala | 11 +-- .../main/scala/sbt/IvyConfigurations.scala | 72 +++++++++++++++++-- ivy/src/main/scala/sbt/SbtExclusionRule.scala | 44 ++++++++++++ .../scala/sbt/impl/DependencyBuilders.scala | 8 ++- .../actions/src/main/scala/sbt/CacheIvy.scala | 8 ++- main/settings/src/main/scala/sbt/Append.scala | 4 ++ main/src/main/scala/sbt/Defaults.scala | 4 +- main/src/main/scala/sbt/Keys.scala | 1 + notes/0.13.8.markdown | 34 ++++++--- .../exclude-dependencies/build.sbt | 35 +++++++++ .../exclude-dependencies/test | 1 + 14 files changed, 232 insertions(+), 34 deletions(-) create mode 100644 ivy/src/main/scala/sbt/SbtExclusionRule.scala create mode 100644 sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/exclude-dependencies/test diff --git a/ivy/src/main/scala/sbt/CrossVersion.scala b/ivy/src/main/scala/sbt/CrossVersion.scala index 79dfe091e..da37da251 100644 --- a/ivy/src/main/scala/sbt/CrossVersion.scala +++ b/ivy/src/main/scala/sbt/CrossVersion.scala @@ -96,6 +96,13 @@ object CrossVersion { def crossName(name: String, cross: String): String = name + "_" + cross + /** Cross-versions `exclude` according to its `crossVersion`. */ + private[sbt] def substituteCross(exclude: SbtExclusionRule, is: Option[IvyScala]): SbtExclusionRule = { + val fopt: Option[String => String] = + is flatMap { i => CrossVersion(exclude.crossVersion, i.scalaFullVersion, i.scalaBinaryVersion) } + exclude.copy(name = applyCross(exclude.name, fopt)) + } + /** Cross-versions `a` according to cross-version function `cross`. */ def substituteCross(a: Artifact, cross: Option[String => String]): Artifact = a.copy(name = applyCross(a.name, cross)) diff --git a/ivy/src/main/scala/sbt/EvictionWarning.scala b/ivy/src/main/scala/sbt/EvictionWarning.scala index 25ad9014b..7279b77da 100644 --- a/ivy/src/main/scala/sbt/EvictionWarning.scala +++ b/ivy/src/main/scala/sbt/EvictionWarning.scala @@ -137,8 +137,9 @@ object EvictionWarning { private[sbt] def processEvictions(module: IvySbt#Module, options: EvictionWarningOptions, reports: Seq[OrganizationArtifactReport]): EvictionWarning = { val directDependencies = module.moduleSettings match { - case x: InlineConfiguration => x.dependencies - case _ => Seq() + case x: InlineConfiguration => x.dependencies + case x: InlineConfigurationWithExcludes => x.dependencies + case _ => Seq() } val pairs = reports map { detail => val evicteds = detail.modules filter { _.evicted } diff --git a/ivy/src/main/scala/sbt/Ivy.scala b/ivy/src/main/scala/sbt/Ivy.scala index c0e7d508a..64afd5a40 100644 --- a/ivy/src/main/scala/sbt/Ivy.scala +++ b/ivy/src/main/scala/sbt/Ivy.scala @@ -163,16 +163,17 @@ final class IvySbt(val configuration: IvyConfiguration) { { val (baseModule, baseConfiguration) = moduleSettings match { - case ic: InlineConfiguration => configureInline(ic, configuration.log) - case ec: EmptyConfiguration => configureEmpty(ec) - case pc: PomConfiguration => configurePom(pc) - case ifc: IvyFileConfiguration => configureIvyFile(ifc) + case ic: InlineConfiguration => configureInline(ic.withExcludes, configuration.log) + case ic: InlineConfigurationWithExcludes => configureInline(ic, configuration.log) + case ec: EmptyConfiguration => configureEmpty(ec) + case pc: PomConfiguration => configurePom(pc) + case ifc: IvyFileConfiguration => configureIvyFile(ifc) } moduleSettings.ivyScala.foreach(IvyScala.checkModule(baseModule, baseConfiguration, configuration.log)) IvySbt.addExtraNamespace(baseModule) (baseModule, baseConfiguration) } - private def configureInline(ic: InlineConfiguration, log: Logger) = + private def configureInline(ic: InlineConfigurationWithExcludes, log: Logger) = { import ic._ val moduleID = newConfiguredModuleID(module, moduleInfo, configurations) @@ -183,6 +184,7 @@ final class IvySbt(val configuration: IvyConfiguration) { val parser = IvySbt.parseIvyXML(ivy.getSettings, IvySbt.wrapped(module, ivyXML), moduleID, defaultConf.name, validate) IvySbt.addMainArtifact(moduleID) IvySbt.addOverrides(moduleID, overrides, ivy.getSettings.getMatcher(PatternMatcher.EXACT)) + IvySbt.addExcludes(moduleID, excludes, ivyScala) val transformedDeps = IvySbt.overrideDirect(dependencies, overrides) IvySbt.addDependencies(moduleID, transformedDeps, parser) (moduleID, parser.getDefaultConf) @@ -433,9 +435,10 @@ private[sbt] object IvySbt { { val sub = CrossVersion(scalaFullVersion, scalaBinaryVersion) m match { - case ec: EmptyConfiguration => ec.copy(module = sub(ec.module)) - case ic: InlineConfiguration => ic.copy(module = sub(ic.module), dependencies = ic.dependencies map sub, overrides = ic.overrides map sub) - case _ => m + case ec: EmptyConfiguration => ec.copy(module = sub(ec.module)) + case ic: InlineConfiguration => ic.copy(module = sub(ic.module), dependencies = ic.dependencies map sub, overrides = ic.overrides map sub) + case ic: InlineConfigurationWithExcludes => ic.copy(module = sub(ic.module), dependencies = ic.dependencies map sub, overrides = ic.overrides map sub) + case _ => m } } @@ -603,6 +606,19 @@ private[sbt] object IvySbt { confs foreach addConfiguration } + def addExcludes(moduleID: DefaultModuleDescriptor, excludes: Seq[SbtExclusionRule], ivyScala: Option[IvyScala]): Unit = + excludes foreach addExclude(moduleID, ivyScala) + def addExclude(moduleID: DefaultModuleDescriptor, ivyScala: Option[IvyScala])(exclude0: SbtExclusionRule): Unit = + { + // this adds _2.11 postfix + val exclude = CrossVersion.substituteCross(exclude0, ivyScala) + val confs = + if (exclude.configurations.isEmpty) moduleID.getConfigurationsNames.toList + else exclude.configurations + val excludeRule = IvyScala.excludeRule(exclude.organization, exclude.name, confs, exclude.artifact) + moduleID.addExcludeRule(excludeRule) + } + def addOverrides(moduleID: DefaultModuleDescriptor, overrides: Set[ModuleID], matcher: PatternMatcher): Unit = overrides foreach addOverride(moduleID, matcher) def addOverride(moduleID: DefaultModuleDescriptor, matcher: PatternMatcher)(overrideDef: ModuleID): Unit = diff --git a/ivy/src/main/scala/sbt/IvyActions.scala b/ivy/src/main/scala/sbt/IvyActions.scala index 3db09e636..31c104081 100644 --- a/ivy/src/main/scala/sbt/IvyActions.scala +++ b/ivy/src/main/scala/sbt/IvyActions.scala @@ -132,9 +132,10 @@ object IvyActions { } private def crossVersionMap(moduleSettings: ModuleSettings): Option[String => String] = moduleSettings match { - case i: InlineConfiguration => CrossVersion(i.module, i.ivyScala) - case e: EmptyConfiguration => CrossVersion(e.module, e.ivyScala) - case _ => None + case i: InlineConfiguration => CrossVersion(i.module, i.ivyScala) + case i: InlineConfigurationWithExcludes => CrossVersion(i.module, i.ivyScala) + case e: EmptyConfiguration => CrossVersion(e.module, e.ivyScala) + case _ => None } def mapArtifacts(module: ModuleDescriptor, cross: Option[String => String], artifacts: Map[Artifact, File]): Seq[(IArtifact, File)] = { @@ -216,7 +217,7 @@ object IvyActions { import config.{ configuration => c, ivyScala, module => mod } import mod.{ id, modules => deps } val base = restrictedCopy(id, true).copy(name = id.name + "$" + label) - val module = new ivySbt.Module(InlineConfiguration(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala)) + val module = new ivySbt.Module(InlineConfigurationWithExcludes(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala)) val report = updateEither(module, c, uwconfig, logicalClock, depDir, log) match { case Right(r) => r case Left(w) => @@ -238,7 +239,7 @@ object IvyActions { val baseModules = modules map { m => restrictedCopy(m, true) } val deps = baseModules.distinct flatMap classifiedArtifacts(classifiers, exclude) val base = restrictedCopy(id, true).copy(name = id.name + classifiers.mkString("$", "_", "")) - val module = new ivySbt.Module(InlineConfiguration(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala, configurations = confs)) + val module = new ivySbt.Module(InlineConfigurationWithExcludes(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala, configurations = confs)) val upConf = new UpdateConfiguration(c.retrieve, true, c.logging) updateEither(module, upConf, uwconfig, logicalClock, depDir, log) match { case Right(r) => r diff --git a/ivy/src/main/scala/sbt/IvyConfigurations.scala b/ivy/src/main/scala/sbt/IvyConfigurations.scala index b3e1dba1b..d064006a0 100644 --- a/ivy/src/main/scala/sbt/IvyConfigurations.scala +++ b/ivy/src/main/scala/sbt/IvyConfigurations.scala @@ -82,15 +82,18 @@ final case class IvyFileConfiguration(file: File, ivyScala: Option[IvyScala], va final case class PomConfiguration(file: File, ivyScala: Option[IvyScala], validate: Boolean, autoScalaTools: Boolean = true) extends ModuleSettings { def noScala = copy(ivyScala = None) } + +// TODO: When we go sbt 1.0 we should rename InlineConfigurationWithExcludes to InlineConfiguration. +@deprecated("Use InlineConfigurationWithExcludes.", "0.13.8") final case class InlineConfiguration(module: ModuleID, moduleInfo: ModuleInfo, dependencies: Seq[ModuleID], overrides: Set[ModuleID] = Set.empty, ivyXML: NodeSeq = NodeSeq.Empty, configurations: Seq[Configuration] = Nil, defaultConfiguration: Option[Configuration] = None, ivyScala: Option[IvyScala] = None, validate: Boolean = false, conflictManager: ConflictManager = ConflictManager.default) extends ModuleSettings { def withConfigurations(configurations: Seq[Configuration]) = copy(configurations = configurations) def noScala = copy(ivyScala = None) -} -@deprecated("Define a module using inline Scala (InlineConfiguration), a pom.xml (PomConfiguration), or an ivy.xml (IvyFileConfiguration).", "0.13.0") -final case class EmptyConfiguration(module: ModuleID, moduleInfo: ModuleInfo, ivyScala: Option[IvyScala], validate: Boolean) extends ModuleSettings { - def noScala = copy(ivyScala = None) + def withExcludes: InlineConfigurationWithExcludes = + InlineConfigurationWithExcludes(this.module, this.moduleInfo, this.dependencies, this.overrides, Nil, this.ivyXML, + this.configurations, this.defaultConfiguration, this.ivyScala, this.validate, this.conflictManager) } object InlineConfiguration { + @deprecated("Use InlineConfigurationWithExcludes.explicitConfigurations.", "0.13.8") def configurations(explicitConfigurations: Iterable[Configuration], defaultConfiguration: Option[Configuration]) = if (explicitConfigurations.isEmpty) { defaultConfiguration match { @@ -101,6 +104,67 @@ object InlineConfiguration { } else explicitConfigurations } + +final class InlineConfigurationWithExcludes private[sbt] (val module: ModuleID, + val moduleInfo: ModuleInfo, + val dependencies: Seq[ModuleID], + val overrides: Set[ModuleID] = Set.empty, + val excludes: Seq[SbtExclusionRule], + val ivyXML: NodeSeq = NodeSeq.Empty, + val configurations: Seq[Configuration] = Nil, + val defaultConfiguration: Option[Configuration] = None, + val ivyScala: Option[IvyScala] = None, + val validate: Boolean = false, + val conflictManager: ConflictManager = ConflictManager.default) extends ModuleSettings { + def withConfigurations(configurations: Seq[Configuration]) = copy(configurations = configurations) + def noScala = copy(ivyScala = None) + + def copy(module: ModuleID = this.module, + moduleInfo: ModuleInfo = this.moduleInfo, + dependencies: Seq[ModuleID] = this.dependencies, + overrides: Set[ModuleID] = this.overrides, + excludes: Seq[SbtExclusionRule] = this.excludes, + ivyXML: NodeSeq = this.ivyXML, + configurations: Seq[Configuration] = this.configurations, + defaultConfiguration: Option[Configuration] = this.defaultConfiguration, + ivyScala: Option[IvyScala] = this.ivyScala, + validate: Boolean = this.validate, + conflictManager: ConflictManager = this.conflictManager): InlineConfigurationWithExcludes = + InlineConfigurationWithExcludes(module, moduleInfo, dependencies, overrides, excludes, ivyXML, + configurations, defaultConfiguration, ivyScala, validate, conflictManager) + +} +object InlineConfigurationWithExcludes { + def apply(module: ModuleID, + moduleInfo: ModuleInfo, + dependencies: Seq[ModuleID], + overrides: Set[ModuleID] = Set.empty, + excludes: Seq[SbtExclusionRule] = Nil, + ivyXML: NodeSeq = NodeSeq.Empty, + configurations: Seq[Configuration] = Nil, + defaultConfiguration: Option[Configuration] = None, + ivyScala: Option[IvyScala] = None, + validate: Boolean = false, + conflictManager: ConflictManager = ConflictManager.default): InlineConfigurationWithExcludes = + new InlineConfigurationWithExcludes(module, moduleInfo, dependencies, overrides, excludes, ivyXML, + configurations, defaultConfiguration, ivyScala, validate, conflictManager) + + def configurations(explicitConfigurations: Iterable[Configuration], defaultConfiguration: Option[Configuration]) = + if (explicitConfigurations.isEmpty) { + defaultConfiguration match { + case Some(Configurations.DefaultIvyConfiguration) => Configurations.Default :: Nil + case Some(Configurations.DefaultMavenConfiguration) => Configurations.defaultMavenConfigurations + case _ => Nil + } + } else + explicitConfigurations +} + +@deprecated("Define a module using inline Scala (InlineConfiguration), a pom.xml (PomConfiguration), or an ivy.xml (IvyFileConfiguration).", "0.13.0") +final case class EmptyConfiguration(module: ModuleID, moduleInfo: ModuleInfo, ivyScala: Option[IvyScala], validate: Boolean) extends ModuleSettings { + def noScala = copy(ivyScala = None) +} + object ModuleSettings { @deprecated("Explicitly select configuration from pom.xml, ivy.xml, or inline Scala.", "0.13.0") def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID, moduleInfo: => ModuleInfo)(baseDirectory: File, log: Logger): ModuleSettings = diff --git a/ivy/src/main/scala/sbt/SbtExclusionRule.scala b/ivy/src/main/scala/sbt/SbtExclusionRule.scala new file mode 100644 index 000000000..6e897a16b --- /dev/null +++ b/ivy/src/main/scala/sbt/SbtExclusionRule.scala @@ -0,0 +1,44 @@ +package sbt + +import sbt.impl.{ GroupID, GroupArtifactID } + +final class SbtExclusionRule( + val organization: String, + val name: String, + val artifact: String, + val configurations: Seq[String], + val crossVersion: CrossVersion) { + + def copy(organization: String = this.organization, + name: String = this.name, + artifact: String = this.artifact, + configurations: Seq[String] = this.configurations, + crossVersion: CrossVersion = this.crossVersion): SbtExclusionRule = + SbtExclusionRule(organization = organization, + name = name, + artifact = artifact, + configurations = configurations, + crossVersion = crossVersion) +} + +object SbtExclusionRule { + def apply(organization: String): SbtExclusionRule = + new SbtExclusionRule(organization, "*", "*", Nil, CrossVersion.Disabled) + + def apply(organization: String, name: String): SbtExclusionRule = + new SbtExclusionRule(organization, name, "*", Nil, CrossVersion.Disabled) + + def apply(organization: String, + name: String, + artifact: String, + configurations: Seq[String], + crossVersion: CrossVersion): SbtExclusionRule = + new SbtExclusionRule(organization, name, artifact, configurations, crossVersion) + + implicit def groupIdToExclusionRule(organization: GroupID): SbtExclusionRule = + SbtExclusionRule(organization.groupID) + implicit def stringToExclusionRule(organization: String): SbtExclusionRule = + SbtExclusionRule(organization) + implicit def groupArtifactIDToExcludsionRule(gaid: GroupArtifactID): SbtExclusionRule = + SbtExclusionRule(gaid.groupID, gaid.artifactID, "*", Nil, gaid.crossVersion) +} diff --git a/ivy/src/main/scala/sbt/impl/DependencyBuilders.scala b/ivy/src/main/scala/sbt/impl/DependencyBuilders.scala index 16a1fa504..c449393f7 100755 --- a/ivy/src/main/scala/sbt/impl/DependencyBuilders.scala +++ b/ivy/src/main/scala/sbt/impl/DependencyBuilders.scala @@ -24,7 +24,7 @@ trait DependencyBuilders { } } -final class GroupID private[sbt] (groupID: String) { +final class GroupID private[sbt] (private[sbt] val groupID: String) { def %(artifactID: String) = groupArtifact(artifactID, CrossVersion.Disabled) def %%(artifactID: String): GroupArtifactID = groupArtifact(artifactID, CrossVersion.binary) @@ -38,10 +38,12 @@ final class GroupID private[sbt] (groupID: String) { nonEmpty(artifactID, "Artifact ID") new GroupArtifactID(groupID, artifactID, cross) } - private[this] def deprecationMessage = """Use the cross method on the constructed ModuleID. For example: ("a" % "b" % "1").cross(...)""" } -final class GroupArtifactID private[sbt] (groupID: String, artifactID: String, crossVersion: CrossVersion) { +final class GroupArtifactID private[sbt] ( + private[sbt] val groupID: String, + private[sbt] val artifactID: String, + private[sbt] val crossVersion: CrossVersion) { def %(revision: String): ModuleID = { nonEmpty(revision, "Revision") diff --git a/main/actions/src/main/scala/sbt/CacheIvy.scala b/main/actions/src/main/scala/sbt/CacheIvy.scala index 91e82107e..b038da256 100644 --- a/main/actions/src/main/scala/sbt/CacheIvy.scala +++ b/main/actions/src/main/scala/sbt/CacheIvy.scala @@ -130,18 +130,22 @@ object CacheIvy { implicit def inlineIvyIC: InputCache[InlineIvyConfiguration] = wrapIn implicit def moduleSettingsIC: InputCache[ModuleSettings] = - unionInputCache[ModuleSettings, PomConfiguration :+: InlineConfiguration :+: EmptyConfiguration :+: IvyFileConfiguration :+: HNil] + unionInputCache[ModuleSettings, PomConfiguration :+: InlineConfiguration :+: InlineConfigurationWithExcludes :+: EmptyConfiguration :+: IvyFileConfiguration :+: HNil] implicit def ivyConfigurationIC: InputCache[IvyConfiguration] = unionInputCache[IvyConfiguration, InlineIvyConfiguration :+: ExternalIvyConfiguration :+: HNil] object L4 { + implicit val inlineWithExcludesToHL = (c: InlineConfigurationWithExcludes) => + c.module :+: c.dependencies :+: c.ivyXML :+: c.configurations :+: c.defaultConfiguration.map(_.name) :+: + c.ivyScala :+: c.validate :+: c.overrides :+: c.excludes :+: HNil implicit def moduleConfToHL = (m: ModuleConfiguration) => m.organization :+: m.name :+: m.revision :+: m.resolver :+: HNil implicit def emptyToHL = (e: EmptyConfiguration) => e.module :+: e.ivyScala :+: e.validate :+: HNil implicit def inlineToHL = (c: InlineConfiguration) => c.module :+: c.dependencies :+: c.ivyXML :+: c.configurations :+: c.defaultConfiguration.map(_.name) :+: c.ivyScala :+: c.validate :+: c.overrides :+: HNil } import L4._ + implicit def inlineWithExcludesIC: InputCache[InlineConfigurationWithExcludes] = wrapIn implicit def emptyIC: InputCache[EmptyConfiguration] = wrapIn implicit def inlineIC: InputCache[InlineConfiguration] = wrapIn implicit def moduleConfIC: InputCache[ModuleConfiguration] = wrapIn @@ -172,6 +176,7 @@ object CacheIvy { implicit def artifactToHL = (a: Artifact) => a.name :+: a.`type` :+: a.extension :+: a.classifier :+: names(a.configurations) :+: a.url :+: a.extraAttributes :+: HNil implicit def exclusionToHL = (e: ExclusionRule) => e.organization :+: e.name :+: e.artifact :+: e.configurations :+: HNil + implicit def sbtExclusionToHL = (e: SbtExclusionRule) => e.organization :+: e.name :+: e.artifact :+: e.configurations :+: e.crossVersion :+: HNil implicit def crossToHL = (c: CrossVersion) => crossToInt(c) :+: HNil /* implicit def deliverConfToHL = (p: DeliverConfiguration) => p.deliverIvyPattern :+: p.status :+: p.configurations :+: HNil @@ -185,6 +190,7 @@ object CacheIvy { implicit def connectionIC: InputCache[SshConnection] = wrapIn implicit def artifactIC: InputCache[Artifact] = wrapIn implicit def exclusionIC: InputCache[ExclusionRule] = wrapIn + implicit def sbtExclusionIC: InputCache[SbtExclusionRule] = wrapIn implicit def crossVersionIC: InputCache[CrossVersion] = wrapIn /* implicit def publishConfIC: InputCache[PublishConfiguration] = wrapIn implicit def deliverConfIC: InputCache[DeliverConfiguration] = wrapIn*/ diff --git a/main/settings/src/main/scala/sbt/Append.scala b/main/settings/src/main/scala/sbt/Append.scala index cd3f33c59..2bf3ba64e 100644 --- a/main/settings/src/main/scala/sbt/Append.scala +++ b/main/settings/src/main/scala/sbt/Append.scala @@ -19,6 +19,10 @@ object Append { def appendValues(a: Seq[T], b: Seq[V]): Seq[T] = a ++ b def appendValue(a: Seq[T], b: V): Seq[T] = a :+ b } + implicit def appendSeqImplicit[T, V <% T]: Sequence[Seq[T], Seq[V], V] = new Sequence[Seq[T], Seq[V], V] { + def appendValues(a: Seq[T], b: Seq[V]): Seq[T] = a ++ (b map { x => (x: T) }) + def appendValue(a: Seq[T], b: V): Seq[T] = a :+ (b: T) + } implicit def appendString: Value[String, String] = new Value[String, String] { def appendValue(a: String, b: String) = a + b } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b8ee12d9b..b60c4fce4 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1055,6 +1055,7 @@ object Classpaths { defaultConfiguration :== Some(Configurations.Compile), dependencyOverrides :== Set.empty, libraryDependencies :== Nil, + excludeDependencies :== Nil, ivyLoggingLevel :== UpdateLogging.Default, ivyXML :== NodeSeq.Empty, ivyValidate :== false, @@ -1233,7 +1234,8 @@ object Classpaths { new IvySbt(conf) } def moduleSettings0: Initialize[Task[ModuleSettings]] = Def.task { - new InlineConfiguration(projectID.value, projectInfo.value, allDependencies.value, dependencyOverrides.value, ivyXML.value, ivyConfigurations.value, defaultConfiguration.value, ivyScala.value, ivyValidate.value, conflictManager.value) + new InlineConfigurationWithExcludes(projectID.value, projectInfo.value, allDependencies.value, dependencyOverrides.value, excludeDependencies.value, + ivyXML.value, ivyConfigurations.value, defaultConfiguration.value, ivyScala.value, ivyValidate.value, conflictManager.value) } private[this] def sbtClassifiersGlobalDefaults = Defaults.globalDefaults(Seq( diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 66171fdcd..a8783ab17 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -304,6 +304,7 @@ object Keys { val dependencyCacheDirectory = TaskKey[File]("dependency-cache-directory", "The base directory for cached dependencies.", DTask) val libraryDependencies = SettingKey[Seq[ModuleID]]("library-dependencies", "Declares managed dependencies.", APlusSetting) val dependencyOverrides = SettingKey[Set[ModuleID]]("dependency-overrides", "Declares managed dependency overrides.", BSetting) + val excludeDependencies = SettingKey[Seq[SbtExclusionRule]]("exclude-dependencies", "Declares managed dependency exclusions.", BSetting) val allDependencies = TaskKey[Seq[ModuleID]]("all-dependencies", "Inter-project and library dependencies.", CTask) val projectDependencies = TaskKey[Seq[ModuleID]]("project-dependencies", "Inter-project dependencies.", DTask) val ivyXML = SettingKey[NodeSeq]("ivy-xml", "Defines inline Ivy XML for configuring dependency management.", BSetting) diff --git a/notes/0.13.8.markdown b/notes/0.13.8.markdown index c04eb76b5..aae8be9cc 100644 --- a/notes/0.13.8.markdown +++ b/notes/0.13.8.markdown @@ -1,20 +1,34 @@ -[@jsuereth]: https://github.com/jsuereth -[@kretes]: https://github.com/kretes -[@j-keck]: https://github.com/j-keck -[1180]: https://github.com/sbt/sbt/issues/1180 -[1180]: https://github.com/sbt/sbt/pull/1702 -[875]: https://github.com/sbt/sbt/issues/875 -[1542]: https://github.com/sbt/sbt/issues/1542 -[1702]: https://github.com/sbt/sbt/pull/1702 -[1746]: https://github.com/sbt/sbt/pull/1746 + [@cunei]: https://github.com/cunei + [@eed3si9n]: https://github.com/eed3si9n + [@gkossakowski]: https://github.com/gkossakowski + [@jsuereth]: https://github.com/jsuereth + [@kretes]: https://github.com/kretes + [@j-keck]: https://github.com/j-keck + [875]: https://github.com/sbt/sbt/issues/875 + [1180]: https://github.com/sbt/sbt/issues/1180 + [1542]: https://github.com/sbt/sbt/issues/1542 + [1702]: https://github.com/sbt/sbt/pull/1702 + [1746]: https://github.com/sbt/sbt/pull/1746 + [1748]: https://github.com/sbt/sbt/issues/1748 ### Improvements - Discovered main classes are now sorted. [#1180][1180] by [@kretes][@kretes] - Implemented a new mechanism of forking javac, whereby errors are captured. Also more likely to run in-process. [#1702][1702] by [@jsuereth][jsuereth] +- Adds project-level dependency exclusions. See below. ## Fixes - Javac warnings now always treated as warnings. [#1702][1702]/[#875][875] by [@jsuereth][jsuereth] - compilerReporter now fed to javac during incremental compilation. [#1542][1542] by [@jsuereth][jsuereth] -- ignore hidden build files from the build. [#1746][1746] by [@j-keck][@j-keck] \ No newline at end of file +- ignore hidden build files from the build. [#1746][1746] by [@j-keck][@j-keck] + +## Project-level dependency exclusions + +sbt 0.13.8 adds project-level dependency exclusions: + + excludeDependencies += "org.apache.logging.log4j" + excludeDependencies += "com.example" %% "foo" + +In the first example, all artifacts from the organization `"org.apache.logging.log4j"` are excluded from the managed dependency. +In the second example, artifacts with the organization `"com.example"` and the name `"foo"` cross versioned to the current `scalaVersion` are excluded. diff --git a/sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt b/sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt new file mode 100644 index 000000000..16bc85772 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/exclude-dependencies/build.sbt @@ -0,0 +1,35 @@ +lazy val check = taskKey[Unit]("check") + +val dispatch = "net.databinder.dispatch" %% "dispatch-core" % "0.11.2" +val repatchTwitter = "com.eed3si9n" %% "repatch-twitter-core" % "dispatch0.11.1_0.1.0" + +lazy val a = (project in file("a")). + settings( + scalaVersion := "2.11.4", + libraryDependencies += dispatch, + excludeDependencies += "org.slf4j" + ) + +lazy val b = (project in file("b")). + settings( + scalaVersion := "2.11.4", + libraryDependencies += repatchTwitter, + excludeDependencies += "net.databinder.dispatch" %% "dispatch-core" + ) + +lazy val root = (project in file(".")). + settings( + check := { + (update in a).value + (update in b).value + val acp = (externalDependencyClasspath in Compile in a).value.sortBy {_.data.getName} + val bcp = (externalDependencyClasspath in Compile in b).value.sortBy {_.data.getName} + + if (acp exists { _.data.getName contains "slf4j-api-1.7.5.jar" }) { + sys.error("slf4j-api-1.7.5.jar found when it should NOT be included: " + acp.toString) + } + if (bcp exists { _.data.getName contains "dispatch-core_2.11-0.11.1.jar" }) { + sys.error("dispatch-core_2.11-0.11.1.jar found when it should NOT be included: " + bcp.toString) + } + } + ) \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/exclude-dependencies/test b/sbt/src/sbt-test/dependency-management/exclude-dependencies/test new file mode 100644 index 000000000..15675b169 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/exclude-dependencies/test @@ -0,0 +1 @@ +> check