Dependency version drift between sbt-iw-projects and mill-iw-support #2

Open
opened 2026-04-08 08:06:01 +00:00 by mprihoda · 0 comments
mprihoda commented 2026-04-08 08:06:01 +00:00 (Migrated from github.com)

Problem

sbt-iw-projects (IWMaterialsVersions.scala) and mill-iw-support (IWMillVersions.scala) maintain two independent lists of library versions. They have silently drifted, and real consumers now hit build breakage.

Concrete incident (2026-04)

xml-rozhrani (sbt) consumes both sbt-iw-projects AND iw-support (the runtime library, built with Mill via mill-iw-support). After iw-support was rebuilt against mill-iw-support 0.1.3 (which bumped several libs), downstream sbt builds broke:

Library sbt-iw-projects mill-iw-support (0.1.3+) Kind of break
magnum 2.0.0-M1 2.0.0-M2 Source-level API change (TransactorTransactorZIO, package moved). Required a code migration in consumers.
zio-json 0.7.43 0.9.0 (reverted to 0.7.44 in mill-iw-support 0.1.4) Binary mismatch with tapir-json-zio 1.13.x, which still declares zio-json 0.7.44.
tapir 1.11.25 1.13.9 Minor break: new abstract method ServerLog.requestHandledByInterceptor. Required consumer to add an override.

mill-iw-support 0.1.3 also bumped zio-http (3.0.0-RC9 → 3.8.1), zio-cli (0.7.2 → 0.8.0), zio-prelude (RC40 → RC46), and others without validation against real consumers. The sbt side was left untouched. Every unsynchronized bump is a latent landmine.

Why it hurts

  1. No single source of truth. When bumping a version, one naturally updates only the plugin one is building with, unaware the other list exists.
  2. Cross-build-tool consumers amplify the problem. Projects using sbt (sbt-iw-projects) that also depend on Mill-built iw-support JARs see the mismatch only at transitive-resolution time, often with confusing eviction errors rather than a clear "these versions are incompatible."
  3. No test harness. Neither plugin validates that the version sets it exposes actually resolve consistently against a real project.

Goal

Eliminate the possibility of drift between sbt-iw-projects and mill-iw-support version lists. A change to one MUST affect the other (or fail a check).

Proposed solutions

Option A — Single shared version file, generated into both plugins

Move the canonical version list into a build-tool-neutral format (plain Scala source, JSON, or TOML) in a shared location (e.g. versions/src/main/resources/iw-versions.json at the monorepo root). Both plugins read from / are generated from it:

  • IWMaterialsVersions.scala (sbt plugin) and IWMillVersions.scala (mill plugin) become either:
    • (A1) thin wrappers that load the shared file at plugin-build time; or
    • (A2) code-generated from the shared file by a small script run pre-commit or in CI.

Pros: truly one source, no duplication.
Cons: build-time complexity; adds a dependency between the two plugin builds.

Option B — Published IW BOM artifact

Publish a iw-bom Maven artifact (Bill of Materials, pom packaging) containing all managed versions. Both plugins reference the BOM for their V.* constants by resolving and reading the published POM at the plugin's own build time; downstream consumers can also import the BOM directly via dependencyManagement.

Pros: standard Maven pattern, externally consumable, works without custom tooling.
Cons: extra release step for the BOM; the plugins still need code that maps BOM entries to their V.foo accessors.

Option C — Convert IWVersions to a cross-compiled sub-project

Extract IWVersions into a tiny Scala module that compiles to a pure-Scala JAR with no framework dependencies. Both sbt-iw-projects and mill-iw-support depend on it at their own build time and re-export the constants.

Pros: strongly typed, compile-time guaranteed consistency.
Cons: either needs publishing (like Option B) or relies on monorepo layout where both plugins can dependsOn the local module.

Option D — Drift-check CI job (lightweight, doesn't solve root cause)

Add a CI job that diffs IWMaterialsVersions.scala against IWMillVersions.scala and fails if corresponding constants differ. Humans still update both manually, but they can't forget.

Pros: trivial to implement, no architectural change.
Cons: still duplicated definitions; drift on version-number differences is caught, but drift via differently-named constants isn't.

Option E — Kill one of the two plugins

If the long-term direction is to standardize on a single build tool across IW projects, deprecate the other plugin and migrate its consumers. This is the nuclear option but eliminates the problem categorically.

Recommendation

Option A2 (code generation from a shared source file) if we want to keep both plugins, with Option D (drift-check CI job) as an immediate safety net until A2 is built. Option A2 because:

  • It's the smallest architectural change.
  • Code-generated files are checked in, so Metals/IDE users don't need runtime resolution magic.
  • No new artifact to publish or version.
  • Both plugin builds fail fast if someone edits a generated file by hand without updating the source.

Immediate bridge

In the meantime, xml-rozhrani has pinned magnum via dependencyOverrides in its build.sbt as a workaround:

ThisBuild / dependencyOverrides += "com.augustnagro" %% "magnumzio" % "2.0.0-M2"
ThisBuild / dependencyOverrides += "com.augustnagro" %% "magnum" % "2.0.0-M2"

This should be removed once sbt-iw-projects catches up.

Latent landmines to audit

Before the next bump, the following mill-iw-support 0.1.3 version changes should be validated against sbt consumers:

  • zio-http 3.0.0-RC9 → 3.8.1 (major RC jump)
  • zio-cli 0.7.2 → 0.8.0
  • zio-prelude 1.0.0-RC40 → RC46
  • tapir 1.11.25 → 1.13.x (caught the ServerLog API change, there may be more)
  • zio-interop-cats 23.1.0.5 → 23.1.0.13

References

  • mill-iw-support mass bump that triggered this: commit f1e96ba "chore: bump dependency versions in mill-iw-support"
  • mill-iw-support 0.1.4 partial fix: commit 5934fd1 "fix(versions): downgrade zio-json to 0.7.44 to match tapir 1.13.15"
  • iw-support 0.1.13 rebuild: commit 779db9b3 "fix(versions): bump mill-iw-support to 0.1.4 for zio-json/tapir alignment"
  • xml-rozhrani 0.2.1 release including the magnum M1→M2 migration and the sbt-side workaround
## Problem `sbt-iw-projects` (`IWMaterialsVersions.scala`) and `mill-iw-support` (`IWMillVersions.scala`) maintain two independent lists of library versions. They have silently drifted, and real consumers now hit build breakage. ### Concrete incident (2026-04) `xml-rozhrani` (sbt) consumes both `sbt-iw-projects` AND `iw-support` (the runtime library, built with Mill via `mill-iw-support`). After `iw-support` was rebuilt against `mill-iw-support 0.1.3` (which bumped several libs), downstream sbt builds broke: | Library | sbt-iw-projects | mill-iw-support (0.1.3+) | Kind of break | |---|---|---|---| | `magnum` | `2.0.0-M1` | `2.0.0-M2` | Source-level API change (`Transactor` → `TransactorZIO`, package moved). Required a code migration in consumers. | | `zio-json` | `0.7.43` | `0.9.0` (reverted to `0.7.44` in mill-iw-support 0.1.4) | Binary mismatch with `tapir-json-zio 1.13.x`, which still declares `zio-json 0.7.44`. | | `tapir` | `1.11.25` | `1.13.9` | Minor break: new abstract method `ServerLog.requestHandledByInterceptor`. Required consumer to add an override. | `mill-iw-support 0.1.3` also bumped `zio-http` (`3.0.0-RC9 → 3.8.1`), `zio-cli` (`0.7.2 → 0.8.0`), `zio-prelude` (`RC40 → RC46`), and others without validation against real consumers. The sbt side was left untouched. Every unsynchronized bump is a latent landmine. ### Why it hurts 1. **No single source of truth.** When bumping a version, one naturally updates only the plugin one is building with, unaware the other list exists. 2. **Cross-build-tool consumers amplify the problem.** Projects using sbt (`sbt-iw-projects`) that also depend on Mill-built `iw-support` JARs see the mismatch only at transitive-resolution time, often with confusing eviction errors rather than a clear "these versions are incompatible." 3. **No test harness.** Neither plugin validates that the version sets it exposes actually resolve consistently against a real project. ## Goal Eliminate the possibility of drift between sbt-iw-projects and mill-iw-support version lists. A change to one MUST affect the other (or fail a check). ## Proposed solutions ### Option A — Single shared version file, generated into both plugins Move the canonical version list into a build-tool-neutral format (plain Scala source, JSON, or TOML) in a shared location (e.g. `versions/src/main/resources/iw-versions.json` at the monorepo root). Both plugins read from / are generated from it: - `IWMaterialsVersions.scala` (sbt plugin) and `IWMillVersions.scala` (mill plugin) become either: - **(A1)** thin wrappers that load the shared file at plugin-build time; or - **(A2)** code-generated from the shared file by a small script run pre-commit or in CI. Pros: truly one source, no duplication. Cons: build-time complexity; adds a dependency between the two plugin builds. ### Option B — Published IW BOM artifact Publish a `iw-bom` Maven artifact (Bill of Materials, `pom` packaging) containing all managed versions. Both plugins reference the BOM for their `V.*` constants by resolving and reading the published POM at the plugin's own build time; downstream consumers can also import the BOM directly via `dependencyManagement`. Pros: standard Maven pattern, externally consumable, works without custom tooling. Cons: extra release step for the BOM; the plugins still need code that maps BOM entries to their `V.foo` accessors. ### Option C — Convert `IWVersions` to a cross-compiled sub-project Extract `IWVersions` into a tiny Scala module that compiles to a pure-Scala JAR with no framework dependencies. Both `sbt-iw-projects` and `mill-iw-support` depend on it at their own build time and re-export the constants. Pros: strongly typed, compile-time guaranteed consistency. Cons: either needs publishing (like Option B) or relies on monorepo layout where both plugins can `dependsOn` the local module. ### Option D — Drift-check CI job (lightweight, doesn't solve root cause) Add a CI job that diffs `IWMaterialsVersions.scala` against `IWMillVersions.scala` and fails if corresponding constants differ. Humans still update both manually, but they can't forget. Pros: trivial to implement, no architectural change. Cons: still duplicated definitions; drift on version-number differences is caught, but drift via differently-named constants isn't. ### Option E — Kill one of the two plugins If the long-term direction is to standardize on a single build tool across IW projects, deprecate the other plugin and migrate its consumers. This is the nuclear option but eliminates the problem categorically. ## Recommendation **Option A2 (code generation from a shared source file)** if we want to keep both plugins, with **Option D (drift-check CI job)** as an immediate safety net until A2 is built. Option A2 because: - It's the smallest architectural change. - Code-generated files are checked in, so Metals/IDE users don't need runtime resolution magic. - No new artifact to publish or version. - Both plugin builds fail fast if someone edits a generated file by hand without updating the source. ## Immediate bridge In the meantime, `xml-rozhrani` has pinned `magnum` via `dependencyOverrides` in its `build.sbt` as a workaround: ```scala ThisBuild / dependencyOverrides += "com.augustnagro" %% "magnumzio" % "2.0.0-M2" ThisBuild / dependencyOverrides += "com.augustnagro" %% "magnum" % "2.0.0-M2" ``` This should be removed once `sbt-iw-projects` catches up. ## Latent landmines to audit Before the next bump, the following `mill-iw-support 0.1.3` version changes should be validated against sbt consumers: - `zio-http 3.0.0-RC9 → 3.8.1` (major RC jump) - `zio-cli 0.7.2 → 0.8.0` - `zio-prelude 1.0.0-RC40 → RC46` - `tapir 1.11.25 → 1.13.x` (caught the `ServerLog` API change, there may be more) - `zio-interop-cats 23.1.0.5 → 23.1.0.13` ## References - mill-iw-support mass bump that triggered this: commit `f1e96ba` "chore: bump dependency versions in mill-iw-support" - mill-iw-support 0.1.4 partial fix: commit `5934fd1` "fix(versions): downgrade zio-json to 0.7.44 to match tapir 1.13.15" - iw-support 0.1.13 rebuild: commit `779db9b3` "fix(versions): bump mill-iw-support to 0.1.4 for zio-json/tapir alignment" - xml-rozhrani 0.2.1 release including the magnum M1→M2 migration and the sbt-side workaround
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/iw-project-support#2
No description provided.