From fcd0bfa7b0df659f36485b60a8084b9b1c52d4b6 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Fri, 1 May 2026 18:36:44 +0200 Subject: [PATCH 1/2] Remove wrongful dependency on scala-library 1.0.29-M3 regression --- build.mill | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.mill b/build.mill index e1a7bfb..55afa55 100644 --- a/build.mill +++ b/build.mill @@ -122,6 +122,11 @@ object proguarded extends CoursierInterfacePublishedModule { def mvnDeps = Seq( Deps.slf4j ) + override def publishXmlDeps = Task { + super.publishXmlDeps().filterNot { dep => + dep.artifact.group == "org.scala-lang" && dep.artifact.id == "scala-library" + } + } object mima extends CoursierInterfaceBinCompatModule { def jar = Task { From 4e3b4a3e86b4b360db1d648fd3216a18d40032ba Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Mon, 4 May 2026 15:46:16 +0200 Subject: [PATCH 2/2] Add test Written with the help of OpenAI Codex + GPT-5.5 --- .github/workflows/ci.yml | 2 + build.mill | 108 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c49aaee..7c5ca8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,8 @@ jobs: jvm: 21 - name: Test run: ./mill -i __.test + - name: Test published dependencies + run: ./mill -i testPublishedDeps mima: runs-on: ${{ matrix.OS }} diff --git a/build.mill b/build.mill index 55afa55..90a47e3 100644 --- a/build.mill +++ b/build.mill @@ -3,14 +3,20 @@ import interfacebuild.* import com.github.lolgab.mill.mima.* +import coursier.{Repositories, Resolve} +import coursier.core.{Dependency, Module, ModuleName, Organization, Repository} +import coursier.ivy.IvyRepository +import coursier.maven.MavenRepository +import coursier.version.VersionConstraint import mill.* import mill.api.BuildCtx +import mill.javalib.publish.{LocalIvyPublisher, LocalM2Publisher} import mill.scalalib.* import mill.util.Jvm import java.util.Locale -import scala.util.Properties +import scala.util.{Properties, Using} object Versions { def scala213 = "2.13.17" @@ -73,6 +79,84 @@ trait CoursierInterfacePublishedModule extends CoursierInterfaceModule with Publ def publishVersion = CoursierInterfaceVersion.buildVersion } +def testPublishedDeps() = Task.Command { + + def checkResolution(name: String, repositories: Seq[Repository]): Unit = { + val version = proguarded.publishVersion() + + val allowed = Set( + s"${CoursierInterfacePublishedModule.organization}:interface:$version", + "org.slf4j:slf4j-api:1.7.36" + ) + + val interfaceDependency = Dependency( + Module( + Organization(CoursierInterfacePublishedModule.organization), + ModuleName("interface"), + Map.empty + ), + VersionConstraint(version) + ) + def resolve(repositories: Seq[Repository]): Set[String] = + val resolve = Resolve() + .withRepositories(repositories) + .addDependencies(interfaceDependency) + val resolution = resolve.io.unsafeRun(true)(using resolve.cache.ec) + resolution.dependenciesWithRetainedVersions.map { dep => + val mod = dep.module + s"${mod.organization.value}:${mod.name.value}:${dep.versionConstraint.asString}" + } + + val modules = resolve(repositories) + val unexpected = modules -- allowed + if (unexpected.nonEmpty) + sys.error( + s"Unexpected dependencies from $name publication:${System.lineSeparator()}" + + unexpected.toSeq.sorted.mkString(System.lineSeparator()) + ) + } + + val publishInfos = proguarded.defaultPublishInfos(sources = true, docs = false)() + val metadata = proguarded.artifactMetadata() + val pom = proguarded.pom().path + + def checkIvy(baseDir: os.Path): Unit = { + val ivyRepo = baseDir / "ivy" + os.makeDir.all(ivyRepo) + + val ivy = proguarded.ivy().path + new LocalIvyPublisher(ivyRepo).publishLocal(pom, Right(ivy), metadata, publishInfos) + + val ivyBase = ivyRepo.toNIO.toUri.toASCIIString.stripSuffix("/") + val ivyRepository = + IvyRepository.parse(s"$ivyBase/[organisation]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]") + .left.map(error => sys.error(s"Invalid Ivy repository pattern: $error")) + .merge + checkResolution( + "publishLocal", + Seq(ivyRepository, Repositories.central) + ) + } + + def checkM2(baseDir: os.Path): Unit = { + val m2Repo = baseDir / "m2" + os.makeDir.all(m2Repo) + + new LocalM2Publisher(m2Repo).publish(pom, metadata, publishInfos) + + val m2Base = m2Repo.toNIO.toUri.toASCIIString + checkResolution("publishM2Local", Seq(MavenRepository(m2Base), Repositories.central)) + } + + val tmp = os.temp.dir(prefix = "coursier-interface-published-deps-") + try { + checkIvy(tmp) + checkM2(tmp) + } + finally + os.remove.all(tmp) +} + trait CoursierInterfaceBinCompatModule extends JavaModule with InterfaceMima { def mimaPreviousVersions = { @@ -127,6 +211,28 @@ object proguarded extends CoursierInterfacePublishedModule { dep.artifact.group == "org.scala-lang" && dep.artifact.id == "scala-library" } } + override def ivy = Task { + import scala.xml.{Elem, XML} + val baseIvy = super.ivy() + val ivyXml = XML.loadFile(baseIvy.path.toIO) + val updated = ivyXml.copy( + child = ivyXml.child.map { + case dependencies: Elem if dependencies.label == "dependencies" => + dependencies.copy( + child = dependencies.child.filter { + case dependency: Elem if dependency.label == "dependency" => + dependency.attribute("org").forall(_.text != "org.scala-lang") || + dependency.attribute("name").forall(_.text != "scala-library") + case _ => true + } + ) + case other => other + } + ) + val dest = Task.dest / "ivy.xml" + XML.save(dest.toString, updated, "UTF-8", xmlDecl = true) + PathRef(dest) + } object mima extends CoursierInterfaceBinCompatModule { def jar = Task {