mirror of https://github.com/sbt/sbt.git
[2.x] feat: Support Maven BOM (Bill of Materials) (#8675)
**Consuming BOMs**
- You can declare a BOM with `.pomOnly()` and versionless deps with `"*"`:
- `libraryDependencies += ("com.fasterxml.jackson" % "jackson-bom" % "2.21.0").pomOnly()`
- `libraryDependencies += "com.fasterxml.jackson.core" % "jackson-core" % "*"`
- BOMs are passed to Coursier via `Resolve.addBom()`; version `"*"` is resolved from the BOM.
**makePom**
- POM-only dependencies are emitted under `<dependencyManagement><dependencies>...</dependencies></dependencyManagement>` with `<type>pom</type>` and `<scope>import</scope>`.
- Dependencies with version `"*"` are emitted without a `<version>` element so Maven uses the BOM-managed version.
**Ivy / publishLocal emulation**
- When publishing to Ivy (e.g. `publishLocal`), BOM-resolved versions (deps that had `"*"`) are written into the published `ivy.xml` as forced dependencies (`force="true"`), so consumers that depend on this module get those versions.
This commit is contained in:
parent
40ef381fa2
commit
4c16466672
|
|
@ -5,6 +5,7 @@ import local.Scripted
|
|||
import java.nio.file.{ Files, Path => JPath }
|
||||
import java.util.Locale
|
||||
import sbt.internal.inc.Analysis
|
||||
import sbt.Tags
|
||||
import com.eed3si9n.jarjarabrams.ModuleCoordinate
|
||||
|
||||
// ThisBuild settings take lower precedence,
|
||||
|
|
@ -74,6 +75,10 @@ def commonSettings: Seq[Setting[?]] = Def.settings(
|
|||
testFrameworks += TestFramework("hedgehog.sbt.Framework"),
|
||||
testFrameworks += TestFramework("verify.runner.Framework"),
|
||||
Global / concurrentRestrictions += Utils.testExclusiveRestriction,
|
||||
// On Windows, limit to one task at a time to avoid OverlappingFileLockException when
|
||||
// multiple tasks (e.g. scalafix plugin and sbt Coursier) write to the same cache.
|
||||
Global / concurrentRestrictions ++= (if (scala.util.Properties.isWin) Seq(Tags.limitAll(1))
|
||||
else Nil),
|
||||
Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
|
||||
Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2"),
|
||||
compile / javacOptions ++= Seq("-Xlint", "-Xlint:-serial"),
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ import lmcoursier.syntax.*
|
|||
import sbt.internal.librarymanagement.IvySbt
|
||||
import sbt.librarymanagement.*
|
||||
import sbt.util.Logger
|
||||
import coursier.core.Dependency
|
||||
import coursier.core.Publication
|
||||
import coursier.core.{ BomDependency, Dependency, Publication }
|
||||
|
||||
import scala.util.{ Try, Failure }
|
||||
|
||||
|
|
@ -217,14 +216,42 @@ class CoursierDependencyResolution(
|
|||
val interProjectRepo = InterProjectRepository(interProjectDependencies)
|
||||
val extraProjectsRepo = InterProjectRepository(extraProjects)
|
||||
|
||||
val dependencies = module0.dependencies
|
||||
// BOM (Bill of Materials): deps with only pom artifact (e.g. .pomOnly()) go to Resolve.addBom (sbt#4531)
|
||||
def isBom(m: ModuleID): Boolean =
|
||||
m.explicitArtifacts.nonEmpty && m.explicitArtifacts.forall(_.`type` == "pom")
|
||||
val (bomModules, regularModules) = module0.dependencies.partition(isBom)
|
||||
val boms: Seq[BomDependency] = bomModules.map { m =>
|
||||
val (mod, ver) =
|
||||
FromSbt.moduleVersion(
|
||||
m,
|
||||
sv,
|
||||
sbv,
|
||||
optionalCrossVer = true,
|
||||
projectPlatform = projectPlatform
|
||||
)
|
||||
BomDependency(ToCoursier.module(mod), ver, Configuration.empty)
|
||||
}
|
||||
// Coursier fills version from BOM only when versionConstraint is empty (Resolution.processedRootDependencies).
|
||||
// So for deps with "*" or "" and BOMs present, pass empty version so BOM can supply it (sbt#4531).
|
||||
val dependencies = regularModules
|
||||
.flatMap { d =>
|
||||
// crossVersion sometimes already taken into account (when called via the update task), sometimes not
|
||||
// (e.g. sbt-dotty 0.13.0-RC1)
|
||||
FromSbt.dependencies(d, sv, sbv, optionalCrossVer = true)
|
||||
FromSbt.dependencies(d, sv, sbv, optionalCrossVer = true, projectPlatform = projectPlatform)
|
||||
}
|
||||
.map { (config, dep) =>
|
||||
(ToCoursier.configuration(config), ToCoursier.dependency(dep))
|
||||
val depForResolve =
|
||||
if (boms.nonEmpty && (dep.version == "*" || dep.version.isEmpty))
|
||||
lmcoursier.definitions.Dependency(
|
||||
dep.module,
|
||||
"",
|
||||
dep.configuration,
|
||||
dep.exclusions,
|
||||
dep.publication,
|
||||
dep.optional,
|
||||
dep.transitive
|
||||
)
|
||||
else
|
||||
dep
|
||||
(ToCoursier.configuration(config), ToCoursier.dependency(depForResolve))
|
||||
}
|
||||
|
||||
val orderedConfigs = Inputs
|
||||
|
|
@ -275,6 +302,7 @@ class CoursierDependencyResolution(
|
|||
strictOpt = conf.strict.map(ToCoursier.strict),
|
||||
missingOk = conf.missingOk,
|
||||
retry = conf.retry.getOrElse(ResolutionParams.defaultRetry),
|
||||
boms = boms,
|
||||
)
|
||||
|
||||
def artifactsParams(resolutions: Map[Configuration, Resolution]): ArtifactsParams =
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ final case class ResolutionParams(
|
|||
params: coursier.params.ResolutionParams,
|
||||
strictOpt: Option[Strict],
|
||||
missingOk: Boolean,
|
||||
retry: (FiniteDuration, Int)
|
||||
retry: (FiniteDuration, Int),
|
||||
boms: Seq[BomDependency] = Nil
|
||||
) {
|
||||
|
||||
lazy val allConfigExtends: Map[Configuration, Set[Configuration]] = {
|
||||
|
|
@ -97,9 +98,10 @@ final case class ResolutionParams(
|
|||
a14,
|
||||
a15,
|
||||
a16,
|
||||
a17
|
||||
a17,
|
||||
a18
|
||||
) =>
|
||||
(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17).##
|
||||
(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18).##
|
||||
}
|
||||
|
||||
// ResolutionParams.unapply(this).get.##
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ object ResolutionRun {
|
|||
dep
|
||||
}
|
||||
)
|
||||
.withBoms(params.boms)
|
||||
.withRepositories(repositories)
|
||||
.withResolutionParams(
|
||||
params.params
|
||||
|
|
|
|||
|
|
@ -97,8 +97,18 @@ class MakePom(val log: Logger) {
|
|||
{extra}
|
||||
{
|
||||
val deps = depsInConfs(module, configurations)
|
||||
val (bomDeps, regularDeps) =
|
||||
deps.partition(d =>
|
||||
d.getAllDependencyArtifacts.nonEmpty &&
|
||||
d.getAllDependencyArtifacts.forall(_.getType == Artifact.PomType)
|
||||
)
|
||||
makeProperties(module, deps) ++
|
||||
makeDependencies(deps, includeTypes, ArraySeq.unsafeWrapArray(module.getAllExcludeRules))
|
||||
makeDependencyManagement(bomDeps) ++
|
||||
makeDependencies(
|
||||
regularDeps,
|
||||
includeTypes,
|
||||
ArraySeq.unsafeWrapArray(module.getAllExcludeRules)
|
||||
)
|
||||
}
|
||||
{makeRepositories(ivy.getSettings, allRepositories, filterRepositories)}
|
||||
</project>)
|
||||
|
|
@ -238,6 +248,28 @@ class MakePom(val log: Logger) {
|
|||
}
|
||||
val IgnoreTypes: Set[String] = Set(Artifact.SourceType, Artifact.DocType, Artifact.PomType)
|
||||
|
||||
/** BOM (Bill of Materials) deps: output under <dependencyManagement> with type pom, scope import (sbt#4531). */
|
||||
def makeDependencyManagement(dependencies: Seq[DependencyDescriptor]): NodeSeq =
|
||||
if (dependencies.isEmpty)
|
||||
NodeSeq.Empty
|
||||
else
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
{dependencies.map(makeBomDependencyElem)}
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
def makeBomDependencyElem(dependency: DependencyDescriptor): Elem = {
|
||||
val mrid = dependency.getDependencyRevisionId
|
||||
<dependency>
|
||||
<groupId>{mrid.getOrganisation}</groupId>
|
||||
<artifactId>{mrid.getName}</artifactId>
|
||||
<version>{makeDependencyVersion(mrid.getRevision)}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
}
|
||||
|
||||
def makeDependencies(
|
||||
dependencies: Seq[DependencyDescriptor],
|
||||
includeTypes: Set[String],
|
||||
|
|
@ -301,16 +333,22 @@ class MakePom(val log: Logger) {
|
|||
excludes: Seq[ExcludeRule]
|
||||
): Elem = {
|
||||
val mrid = dependency.getDependencyRevisionId
|
||||
<dependency>
|
||||
<groupId>{mrid.getOrganisation}</groupId>
|
||||
<artifactId>{mrid.getName}</artifactId>
|
||||
<version>{makeDependencyVersion(mrid.getRevision)}</version>
|
||||
{scopeElem(scope)}
|
||||
{optionalElem(optional)}
|
||||
{classifierElem(classifier)}
|
||||
{typeElem(tpe)}
|
||||
{exclusions(dependency, excludes)}
|
||||
</dependency>
|
||||
val rev = mrid.getRevision
|
||||
val versionNode: NodeSeq =
|
||||
if (rev == null || rev == "*" || rev.isEmpty) NodeSeq.Empty
|
||||
else <version>{makeDependencyVersion(rev)}</version>
|
||||
val result: Elem =
|
||||
<dependency>
|
||||
<groupId>{mrid.getOrganisation}</groupId>
|
||||
<artifactId>{mrid.getName}</artifactId>
|
||||
{versionNode}
|
||||
{scopeElem(scope)}
|
||||
{optionalElem(optional)}
|
||||
{classifierElem(classifier)}
|
||||
{typeElem(tpe)}
|
||||
{exclusions(dependency, excludes)}
|
||||
</dependency>
|
||||
result
|
||||
}
|
||||
|
||||
def artifactType(artifact: DependencyArtifactDescriptor): Option[String] =
|
||||
|
|
|
|||
|
|
@ -2973,6 +2973,24 @@ object Classpaths {
|
|||
deliver := deliverTask(makeIvyXmlConfiguration).value,
|
||||
deliverLocal := deliverTask(makeIvyXmlLocalConfiguration).value,
|
||||
makeIvyXml := deliverTask(makeIvyXmlConfiguration).value,
|
||||
resolvedDependencies := Def.task {
|
||||
val report = update.value
|
||||
val deps = allDependencies.value
|
||||
val starDeps = deps.filter(d => d.revision == "*" || d.revision.isEmpty)
|
||||
val compileModules = report.configurations
|
||||
.find(_.configuration.name == "compile")
|
||||
.toVector
|
||||
.flatMap(_.modules)
|
||||
starDeps.flatMap { d =>
|
||||
compileModules
|
||||
.find(m =>
|
||||
m.module.organization == d.organization &&
|
||||
m.module.name == d.name &&
|
||||
!m.evicted
|
||||
)
|
||||
.map(m => d.withRevision(m.module.revision))
|
||||
}.distinct
|
||||
}.value,
|
||||
publish := publishOrSkip(publishConfiguration, publish / skip).value,
|
||||
publishLocal := LibraryManagement.ivylessPublishLocalTask.value,
|
||||
publishM2 := publishOrSkip(publishM2Configuration, publishM2 / skip).value,
|
||||
|
|
|
|||
|
|
@ -566,6 +566,8 @@ object Keys {
|
|||
val deliverLocal = taskKey[File]("Generates the Ivy file for publishing to the local repository.").withRank(BTask)
|
||||
// makeIvyXml is currently identical to the confusingly-named "deliver", which may be deprecated in the future
|
||||
val makeIvyXml = taskKey[File]("Generates the Ivy file for publishing to a repository.").withRank(BTask)
|
||||
/** BOM-resolved ModuleIDs for deps that had \"*\" (version from BOM); emitted as forced deps in published ivy.xml (sbt#4531). */
|
||||
val resolvedDependencies = taskKey[Seq[ModuleID]]("")
|
||||
val publish = taskKey[Unit]("Publishes artifacts to a repository.").withRank(APlusTask)
|
||||
val publishLocal = taskKey[Unit]("Publishes artifacts to the local Ivy repository.").withRank(APlusTask)
|
||||
val publishM2 = taskKey[Unit]("Publishes artifacts to the local Maven repository.").withRank(ATask)
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ object LMCoursier {
|
|||
val sv = scalaVersion.value
|
||||
val lockFile = dependencyLockFile.value
|
||||
val lockFileOpt = if (lockFile.exists()) Some(lockFile) else None
|
||||
val ivyHomeOpt = ivyPaths.value.ivyHome.map(new File(_))
|
||||
coursierConfiguration(
|
||||
csrRecursiveResolvers.value,
|
||||
csrInterProjectDependencies.value.toVector,
|
||||
|
|
@ -170,7 +171,7 @@ object LMCoursier {
|
|||
csrLogger.value,
|
||||
csrCacheDirectory.value,
|
||||
csrReconciliations.value,
|
||||
ivyPaths.value.ivyHome.map(new File(_)),
|
||||
ivyHomeOpt,
|
||||
CoursierInputsTasks.strictTask.value,
|
||||
dependencyOverrides.value,
|
||||
Some(updateConfiguration.value),
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ object IvyXml {
|
|||
|
||||
private def rawContent(
|
||||
currentProject: Project,
|
||||
shadedConfigOpt: Option[Configuration]
|
||||
shadedConfigOpt: Option[Configuration],
|
||||
bomForcedDeps: Seq[(String, String, String)]
|
||||
): String = {
|
||||
|
||||
// Important: width = Int.MaxValue, so that no tag gets truncated.
|
||||
|
|
@ -38,7 +39,7 @@ object IvyXml {
|
|||
val printer = new scala.xml.PrettyPrinter(Int.MaxValue, 2)
|
||||
|
||||
"""<?xml version="1.0" encoding="UTF-8"?>""" + '\n' +
|
||||
printer.format(content(currentProject, shadedConfigOpt))
|
||||
printer.format(content(currentProject, shadedConfigOpt, bomForcedDeps))
|
||||
}
|
||||
|
||||
// These are required for publish to be fine, later on.
|
||||
|
|
@ -46,8 +47,10 @@ object IvyXml {
|
|||
currentProject: Project,
|
||||
shadedConfigOpt: Option[Configuration],
|
||||
ivySbt: IvySbt,
|
||||
log: sbt.util.Logger
|
||||
log: sbt.util.Logger,
|
||||
resolvedDeps: Seq[sbt.librarymanagement.ModuleID]
|
||||
): Unit = {
|
||||
val bomForcedDeps = resolvedDeps.map(m => (m.organization, m.name, m.revision))
|
||||
|
||||
val ivyCacheManager = ivySbt.withIvy(log)(ivy => ivy.getResolutionCacheManager)
|
||||
|
||||
|
|
@ -61,7 +64,7 @@ object IvyXml {
|
|||
val cacheIvyFile = ivyCacheManager.getResolvedIvyFileInCache(ivyModule)
|
||||
val cacheIvyPropertiesFile = ivyCacheManager.getResolvedIvyPropertiesInCache(ivyModule)
|
||||
|
||||
val content0 = rawContent(currentProject, shadedConfigOpt)
|
||||
val content0 = rawContent(currentProject, shadedConfigOpt, bomForcedDeps)
|
||||
cacheIvyFile.getParentFile.mkdirs()
|
||||
log.debug(s"writing Ivy file $cacheIvyFile")
|
||||
Files.write(cacheIvyFile.toPath, content0.getBytes(UTF_8))
|
||||
|
|
@ -72,7 +75,11 @@ object IvyXml {
|
|||
()
|
||||
}
|
||||
|
||||
private def content(project0: Project, shadedConfigOpt: Option[Configuration]): Node = {
|
||||
private def content(
|
||||
project0: Project,
|
||||
shadedConfigOpt: Option[Configuration],
|
||||
bomForcedDeps: Seq[(String, String, String)]
|
||||
): Node = {
|
||||
|
||||
val filterOutDependencies =
|
||||
shadedConfigOpt.toSet[Configuration].flatMap { shadedConfig =>
|
||||
|
|
@ -144,6 +151,7 @@ object IvyXml {
|
|||
n
|
||||
}
|
||||
|
||||
val bomForcedSet = bomForcedDeps.toSet
|
||||
val dependencyElems = project.dependencies.toVector.map { (conf, dep) =>
|
||||
val classifier = {
|
||||
val pub = dep.publication
|
||||
|
|
@ -165,10 +173,18 @@ object IvyXml {
|
|||
} name="*" type="*" ext="*" conf="" matcher="exact"/>
|
||||
}
|
||||
|
||||
val org0 = dep.module.organization.value
|
||||
val name0 = dep.module.name.value
|
||||
val rev0 = dep.version
|
||||
val forced = bomForcedSet((org0, name0, rev0))
|
||||
val forceAttr =
|
||||
if (forced) new scala.xml.UnprefixedAttribute("force", "true", scala.xml.Null)
|
||||
else scala.xml.Null
|
||||
|
||||
val n =
|
||||
<dependency org={dep.module.organization.value} name={dep.module.name.value} rev={
|
||||
dep.version
|
||||
} conf={s"${conf.value}->${dep.configuration.value}"}>
|
||||
<dependency org={org0} name={name0} rev={rev0} conf={
|
||||
s"${conf.value}->${dep.configuration.value}"
|
||||
}>
|
||||
{classifier}
|
||||
{excludes}
|
||||
</dependency>
|
||||
|
|
@ -178,7 +194,7 @@ object IvyXml {
|
|||
new PrefixedAttribute("e", k, v, acc)
|
||||
}
|
||||
|
||||
n % moduleAttrs
|
||||
n % moduleAttrs % forceAttr
|
||||
}
|
||||
|
||||
<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
|
||||
|
|
@ -200,11 +216,13 @@ object IvyXml {
|
|||
val publications = csrPublications.value
|
||||
proj.withPublications(publications)
|
||||
}
|
||||
val resolved = sbt.Keys.resolvedDependencies.value
|
||||
IvyXml.writeFiles(
|
||||
currentProject,
|
||||
shadedConfigOpt,
|
||||
sbt.Keys.ivySbt.value,
|
||||
sbt.Keys.streams.value.log
|
||||
sbt.Keys.streams.value.log,
|
||||
resolved
|
||||
)
|
||||
}
|
||||
}.value)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
object A
|
||||
|
|
@ -0,0 +1 @@
|
|||
object B
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// BOM + publishLocal (sbt#4531): a uses BOM + jackson-core "*"; b depends on a.
|
||||
// For publishLocal, a's published ivy may still list jackson-core:*; so b also needs the BOM
|
||||
// to resolve that transitive * (per eed3si9n: BOM needs to be added to all subprojects).
|
||||
// Use `common,` not `common*`—compiler's vararg hint is misleading; sbt accepts Seq here.
|
||||
ThisBuild / csrCacheDirectory := (ThisBuild / baseDirectory).value / "coursier-cache"
|
||||
ThisBuild / organization := "org.example"
|
||||
ThisBuild / version := "1.0"
|
||||
ThisBuild / scalaVersion := "2.12.18"
|
||||
|
||||
lazy val a = project
|
||||
.settings(
|
||||
common,
|
||||
libraryDependencies += ("com.fasterxml.jackson" % "jackson-bom" % "2.21.0").pomOnly(),
|
||||
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-core" % "*",
|
||||
)
|
||||
|
||||
lazy val b = project
|
||||
.settings(
|
||||
common,
|
||||
libraryDependencies += ("com.fasterxml.jackson" % "jackson-bom" % "2.21.0").pomOnly(),
|
||||
libraryDependencies += organization.value %% "a" % version.value,
|
||||
TaskKey[Unit]("checkBomFromA") := {
|
||||
val report = (Compile / updateFull).value
|
||||
val compileConfig = report.configurations.find(_.configuration.name == "compile").getOrElse(
|
||||
sys.error("compile configuration not found")
|
||||
)
|
||||
val jacksonCore = compileConfig.modules.find(_.module.name == "jackson-core").getOrElse(
|
||||
sys.error("jackson-core not found in update report (expected from a's published ivy)")
|
||||
)
|
||||
val expected = "2.21.0"
|
||||
if (jacksonCore.module.revision != expected)
|
||||
sys.error(s"Expected jackson-core $expected from a's BOM-resolved ivy, got ${jacksonCore.module.revision}")
|
||||
},
|
||||
)
|
||||
|
||||
lazy val common = Seq(
|
||||
ivyPaths := IvyPaths(baseDirectory.value.toString, Some(((ThisBuild / baseDirectory).value / "ivy" / "cache").toString)),
|
||||
)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
> a/publishLocal
|
||||
> b/update
|
||||
> b/checkBomFromA
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
ThisBuild / csrCacheDirectory := (ThisBuild / baseDirectory).value / "coursier-cache"
|
||||
|
||||
// BOM (Bill of Materials) consumption: .pomOnly() + version "*" (sbt#4531)
|
||||
scalaVersion := "2.12.18"
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
("com.fasterxml.jackson" % "jackson-bom" % "2.17.0").pomOnly(),
|
||||
"com.fasterxml.jackson.core" % "jackson-core" % "*"
|
||||
)
|
||||
|
||||
TaskKey[Unit]("checkBomResolved") := {
|
||||
val report = (Compile / updateFull).value
|
||||
val compileConfig = report.configurations.find(_.configuration.name == "compile").getOrElse(
|
||||
sys.error("compile configuration not found")
|
||||
)
|
||||
val jacksonCoreReport = compileConfig.modules.find(_.module.name == "jackson-core").getOrElse(
|
||||
sys.error("jackson-core not found in update report")
|
||||
)
|
||||
val expectedVersion = "2.17.0"
|
||||
if (jacksonCoreReport.module.revision != expectedVersion)
|
||||
sys.error(s"Expected jackson-core version $expectedVersion from BOM, got ${jacksonCoreReport.module.revision}")
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
> update
|
||||
> checkBomResolved
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
lazy val p1 = (project in file("p1")).
|
||||
settings(
|
||||
checkTask(expectedMongo),
|
||||
checkTask(expectedMongo, fromDependencyManagement = true),
|
||||
libraryDependencies += ("org.mongodb" %% "casbah" % "2.4.1").pomOnly(),
|
||||
inThisBuild(List(
|
||||
organization := "org.example",
|
||||
|
|
@ -16,12 +16,14 @@ lazy val p2 = (project in file("p2")).
|
|||
checkTask(expectedInter)
|
||||
)
|
||||
|
||||
// BOM (sbt#4531): .pomOnly() deps are emitted under <dependencyManagement> with type=pom, scope=import
|
||||
lazy val expectedMongo =
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>casbah_2.9.2</artifactId>
|
||||
<version>2.4.1</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
lazy val expectedInter =
|
||||
|
|
@ -31,15 +33,15 @@ lazy val expectedInter =
|
|||
<version>1.0</version>
|
||||
</dependency>
|
||||
|
||||
def checkTask(expectedDep: xml.Elem) = TaskKey[Unit]("checkPom") := {
|
||||
def checkTask(expectedDep: xml.Elem, fromDependencyManagement: Boolean = false) = TaskKey[Unit]("checkPom") := {
|
||||
val vf = makePom.value
|
||||
val converter = fileConverter.value
|
||||
val pom = xml.XML.loadFile(converter.toPath(vf).toFile)
|
||||
val actual = pom \\ "dependencies"
|
||||
val actual = if (fromDependencyManagement) pom \ "dependencyManagement" \ "dependencies" else pom \ "dependencies"
|
||||
val expected = <d>
|
||||
{expectedDep}
|
||||
</d>
|
||||
def dropTopElem(s:String): String = s.split("""\n""").drop(1).dropRight(1).mkString("\n")
|
||||
def dropTopElem(s: String): String = s.split("""\n""").drop(1).dropRight(1).mkString("\n")
|
||||
val pp = new xml.PrettyPrinter(Int.MaxValue, 0)
|
||||
val expectedString = dropTopElem(pp.format(expected))
|
||||
val actualString = dropTopElem(pp.formatNodes(actual))
|
||||
|
|
|
|||
Loading…
Reference in New Issue