From 7732b0fa1588d7e6b4dd70a5016458ac193fa222 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 9 Sep 2024 10:21:01 +0200 Subject: [PATCH] Support `exclude` attribute in `Dep` parser (#3492) You can give exclusions with `;exclude=org:name` or `;exclude=org:*` or `;exclude=*:name`. Pull request: https://github.com/com-lihaoyi/mill/pull/3492 --- .../ROOT/pages/Library_Dependencies.adoc | 17 ++++++++++++++++- scalalib/src/mill/scalalib/Dep.scala | 13 +++++++++++-- .../src/mill/scalalib/ResolveDepsTests.scala | 6 +++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/Library_Dependencies.adoc b/docs/modules/ROOT/pages/Library_Dependencies.adoc index e0baba3c380..cff5e929e8a 100644 --- a/docs/modules/ROOT/pages/Library_Dependencies.adoc +++ b/docs/modules/ROOT/pages/Library_Dependencies.adoc @@ -10,12 +10,18 @@ For more details about coursier, refer to the {link-coursier-doc}[coursier docum == Dependencies in General -Mill dependencies have the form: +Mill dependencies have the simple form: ---- ivy"{organization}:{name}:{version}" ---- +Additional attributes are also supported: + +---- +ivy"{organization}:{name}:{version}[;{attribute}={value}]*" +---- + When working in other Java and Scala projects, you will find some synonyms, which typically all mean the same. For example in the Maven ecosystem, the `organization` is called the `group` and the `name` is called the `artifact`. @@ -225,6 +231,15 @@ def deps = Agg( You can also use `.excludeOrg` or `excludeName`: +There is also a short notation available: + +.Example: Shot notation to exclude `fansi_2.12` library from transitive dependency set of `pprint`. +[source,scala] +---- +def deps = Agg( + ivy"com.lihaoyi::pprint:0.5.3;exclude=com.lihaoyi:fansi_2.12" +) +---- .Example: Exclude all `com.lihaoyi` libraries from transitive dependency set of `pprint`. [source,scala] diff --git a/scalalib/src/mill/scalalib/Dep.scala b/scalalib/src/mill/scalalib/Dep.scala index e00e5a74b21..b857131bc50 100644 --- a/scalalib/src/mill/scalalib/Dep.scala +++ b/scalalib/src/mill/scalalib/Dep.scala @@ -106,14 +106,17 @@ object Dep { implicit def parse(signature: String): Dep = { val parts = signature.split(';') val module = parts.head + var exclusions = Seq.empty[(String, String)] val attributes = parts.tail.foldLeft(coursier.Attributes()) { (as, s) => s.split('=') match { case Array("classifier", v) => as.withClassifier(coursier.Classifier(v)) case Array("type", v) => as.withType(coursier.Type(v)) + case Array("exclude", s"${org}:${name}") => exclusions ++= Seq((org, name)); as case Array(k, v) => throw new Exception(s"Unrecognized attribute: [$s]") case _ => throw new Exception(s"Unable to parse attribute specifier: [$s]") } } + (module.split(':') match { case Array(a, b, c) => Dep(a, b, c, cross = empty(platformed = false)) case Array(a, b, "", c) => Dep(a, b, c, cross = empty(platformed = true)) @@ -122,7 +125,9 @@ object Dep { case Array(a, "", "", b, c) => Dep(a, b, c, cross = Full(platformed = false)) case Array(a, "", "", b, "", c) => Dep(a, b, c, cross = Full(platformed = true)) case _ => throw new Exception(s"Unable to parse signature: [$signature]") - }).configure(attributes = attributes) + }) + .exclude(exclusions.sorted: _*) + .configure(attributes = attributes) } @unused private implicit val depFormat: RW[Dependency] = mill.scalalib.JsonFormatters.depFormat @@ -141,7 +146,11 @@ object Dep { case "" => "" case s => s";type=$s" } - val attrs = classifierAttr + typeAttr + + val excludeAttr = + dep.dep.exclusions().toSeq.sorted.map(e => s";exclude=${e._1.value}:${e._2.value}").mkString + + val attrs = classifierAttr + typeAttr + excludeAttr val prospective = dep.cross match { case CrossVersion.Constant("", false) => Some(s"$org:$mod:$ver$attrs") diff --git a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala index 98be5c2efba..cd55d1b14b5 100644 --- a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala +++ b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala @@ -54,14 +54,14 @@ object ResolveDepsTests extends TestSuite { test("excludeTransitiveDeps") { val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".exclude("com.lihaoyi" -> "fansi_2.12")) - assertRoundTrip(deps, simplified = false) + assertRoundTrip(deps, simplified = true) val Success(paths) = evalDeps(deps) assert(!paths.exists(_.path.toString.contains("fansi_2.12"))) } test("excludeTransitiveDepsByOrg") { val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".excludeOrg("com.lihaoyi")) - assertRoundTrip(deps, simplified = false) + assertRoundTrip(deps, simplified = true) val Success(paths) = evalDeps(deps) assert(!paths.exists(path => path.path.toString.contains("com/lihaoyi") && !path.path.toString.contains("pprint_2.12") @@ -70,7 +70,7 @@ object ResolveDepsTests extends TestSuite { test("excludeTransitiveDepsByName") { val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".excludeName("fansi_2.12")) - assertRoundTrip(deps, simplified = false) + assertRoundTrip(deps, simplified = true) val Success(paths) = evalDeps(deps) assert(!paths.exists(_.path.toString.contains("fansi_2.12"))) }