Pin testLatestDeps versions for reproducible builds#16344
Pin testLatestDeps versions for reproducible builds#16344zeitlinger wants to merge 45 commits intoopen-telemetry:mainfrom
Conversation
|
btw, nice idea 👍 |
- Fail-fast when a range-specific pinned version key (e.g. "com.example:lib#2.+") is missing from the JSON instead of silently falling back to the base key which could resolve to a wrong major version. - Make test-latest-deps a required status check now that versions are pinned. Added TODO for branch protection settings (requires admin). Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
|
Merged your fork PR #9 (shared
|
- Remove unnecessary TODO about branch protection (trask feedback) - Cap muzzle open-ended version ranges using pinned versions from latest-dep-versions.json to prevent failures from newly released library versions (trask feedback) - Document version pinning in docs/safety-mechanisms.md Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
|
sent a PR with a bit more feedback: zeitlinger#12 |
- Fail-fast when a range-specific pinned version key (e.g. "com.example:lib#2.+") is missing from the JSON instead of silently falling back to the base key which could resolve to a wrong major version. - Make test-latest-deps a required status check now that versions are pinned. Added TODO for branch protection settings (requires admin). Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
- Remove unnecessary TODO about branch protection (trask feedback) - Cap muzzle open-ended version ranges using pinned versions from latest-dep-versions.json to prevent failures from newly released library versions (trask feedback) - Document version pinning in docs/safety-mechanisms.md Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
4c5245a to
0b4adf1
Compare
- Fail-fast when a range-specific pinned version key (e.g. "com.example:lib#2.+") is missing from the JSON instead of silently falling back to the base key which could resolve to a wrong major version. - Make test-latest-deps a required status check now that versions are pinned. Added TODO for branch protection settings (requires admin). Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
- Remove unnecessary TODO about branch protection (trask feedback) - Cap muzzle open-ended version ranges using pinned versions from latest-dep-versions.json to prevent failures from newly released library versions (trask feedback) - Document version pinning in docs/safety-mechanisms.md Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
e61e46e to
f2ab30e
Compare
- Fail-fast when a range-specific pinned version key (e.g. "com.example:lib#2.+") is missing from the JSON instead of silently falling back to the base key which could resolve to a wrong major version. - Make test-latest-deps a required status check now that versions are pinned. Added TODO for branch protection settings (requires admin). Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
- Remove unnecessary TODO about branch protection (trask feedback) - Cap muzzle open-ended version ranges using pinned versions from latest-dep-versions.json to prevent failures from newly released library versions (trask feedback) - Document version pinning in docs/safety-mechanisms.md Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
|
Muzzle artifacts that fall back to dynamic Aether resolution Six muzzle artifacts can't be pinned in
Items 3–5 are wrong/relocated coordinates in muzzle directives. We could add a coordinate-remapping table in the resolve task to pin these under their original keys (resolving via the correct coordinates). That would reduce the unpinnable set to just 3 (grizzly, restlet, opensearch). @trask Worth adding coordinate remapping, or keep it simple with the Aether fallback for all 6? |
can we just fix the muzzle blocks to fix these? |
good idea - done in addition
|
- Fail-fast when a range-specific pinned version key (e.g. "com.example:lib#2.+") is missing from the JSON instead of silently falling back to the base key which could resolve to a wrong major version. - Make test-latest-deps a required status check now that versions are pinned. Added TODO for branch protection settings (requires admin). Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
- Remove unnecessary TODO about branch protection (trask feedback) - Cap muzzle open-ended version ranges using pinned versions from latest-dep-versions.json to prevent failures from newly released library versions (trask feedback) - Document version pinning in docs/safety-mechanisms.md Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
8debde7 to
887fade
Compare
When -PtestLatestDeps=true, dependency versions are now pinned via .github/config/latest-dep-versions.json instead of resolving dynamically at build time. This makes builds reproducible across time. A new resolveLatestDepVersions task populates the JSON file, and a daily CI workflow opens a PR when versions change. The -PresolveLatestDeps=true flag bypasses pinning for live resolution. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
…docs Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Remove trailing blank line in build.gradle.kts for spotless. Use prefer instead of require for library/testLibrary version upgrades when pinLatestDeps is enabled. This prevents conflicts when a latestDepTestLibrary dependency uses strictly to constrain to an older major version (e.g. spring-boot-starter-test 3.3.x) while the pinned latest version is a newer major (4.0.x). Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Two bugs in resolveLatestDepVersions: - When a range (e.g. activej-http#6.+) has only pre-release versions, fall back to the resolved pre-release instead of skipping the entry entirely. - When latest.release for a muzzle artifact resolves to a broken version (grizzly 5.0.0 depends on a SNAPSHOT BOM), fall back to the highest already-pinned version for that artifact instead of emitting no entry. Also regenerate latest-dep-versions.json. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
- Fix muzzle: update otel-annotations muzzle group from io.opentelemetry to io.opentelemetry.instrumentation (correct Maven Central group) and add the corresponding pinned version entry - Fix testLatestDeps: revert async-http-client to 3.0.7; 3.0.8 has a broken POM declaring netty-codec-http2:4.0.34.Final which doesn't exist (codec-http2 only starts at Netty 4.1.x) - Fix resolveLatestDepVersions task: seed versions map from existing JSON so entries that fail to resolve are preserved rather than deleted, and add transitive-dep check to reject broken versions like 3.0.8 Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
- com.typesafe.akka:akka-actor_2.12 (latest.release is pre-release 2.9.0-M1) - io.netty:netty-all - io.opentelemetry:opentelemetry-instrumentation-annotations (sentinel 0.0 — artifact doesn't exist; this muzzle directive is intentionally a no-op, tests verify compatibility instead) - javax.servlet:servlet-api (latest.release is pre-release 3.0-alpha-1) - org.apache.pekko:pekko-actor_3 and pekko-http_3 (latest.release is pre-release) - org.opensearch.client:rest (sentinel 0.0 — artifact doesn't exist; fail directive) - org.restlet.jse:org.restlet (custom Maven repo, not auto-resolvable) - software.amazon.awssdk:bedrockruntime (fixes muzzle directive typo: bedrock-runtime) Also add "+" fallback in resolveLatestDepVersions for cases where maven <release> metadata points to a pre-release version. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
async-http-client 3.x depends on io.netty:*:4.0.34.Final for modules (netty-handler-proxy, netty-resolver-dns) that only exist from Netty 4.1.x, so the entire 3.x line is unresolvable. Pin to 2.12.4, the latest working release. Also add AcceptableVersions.isStable() to filterVersions() so that pre-release versions (RC, alpha, beta) are excluded from muzzle testing. Without this, assertInverse would fail when a pre-release of a supported version (e.g. 2.0.0-RC15) has a compatible API — producing a spurious "instrumentation unexpectedly passed" error. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
PR open-telemetry#17103 already removed this from main, but this branch pre-dated that fix and had re-introduced the line. When main was merged back in, the line survived because open-telemetry#17103 touched a different region of the file. extra["testLatestDeps"] stored a Boolean, which Gradle's findProperty() returns with higher precedence than the -P project property. All build scripts check findProperty("testLatestDeps") == "true", but Boolean(true) != "true" in Kotlin, so latestDepTest was always false. This caused the Netty force-to-4.0.34.Final block in async-http-client-2.0's build.gradle.kts to fire even in testLatestDeps mode, breaking dependency resolution since that old Netty version is gone from Maven Central.
quarkus-3.0-testing and quarkus-3.9-testing use version ranges "3.5.+" and "3.9.+" in testLatestDeps mode. The pin mechanism requires explicit entries for range keys or it throws an error. Latest stable: 3.5.3 and 3.9.5 (from Maven Central). Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
…deps fail Two bugs caused pre-release versions (e.g. mongodb-driver-sync:5.7.0-beta1) to get pinned instead of the latest stable: 1. resolveStableVersion() checked for unresolvable transitive dependencies, returning null if any were found (e.g. classifier-specific native JARs). Fix: disable transitive resolution — we only need the artifact itself. 2. recordVersion() used a pure numeric comparison, so a stable 5.6.4 would never replace a pre-release 5.7.0-beta1 already in the JSON. Fix: prefer stable over pre-release regardless of numeric ordering. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
…ase pin) Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
8e040ab to
3fce9ba
Compare
|
@trask please have another look? |
trask
left a comment
There was a problem hiding this comment.
Purely AI review, I take no credit or accountability 😂 (see #17889)...
Nice cleanup — pinning testLatestDeps and capping muzzle ranges with the same JSON gives us reproducible CI and removes the release-branch carve-out cleanly. A couple of small nits below; nothing blocking.
- Move AcceptableVersions import below the kdoc so the import block is contiguous with the file header comment. - Drop unused system/session/repos params from resolveUpperBound (the body only consults the pinned-versions map) and update the two callsites in inverseOf and muzzleDirectiveToArtifacts. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
The 5.5.1 pin was added to work around a test failure with later versions, but pinning testLatestDeps versions in the PR's JSON config resolves httpclient5 to 5.6.1 where the test passes. Verified locally: ./gradlew :instrumentation:apache-httpclient:apache-httpclient-5.0:javaagent:test -PtestLatestDeps=true BUILD SUCCESSFUL.
|
CI failing due to #18217 |
8c474cf to
9fd1700
Compare
Summary
testLatestDepsbuilds so that the same commit always resolves the same dependency versions.github/config/latest-dep-versions.json) stores resolved versions, used by the convention plugin and settings to replace dynamic versions (latest.release,+ranges)resolveLatestDepVersionsGradle task and a daily CI workflow to keep the pinned versions up to dateDetails
When
-PtestLatestDeps=true, dependency versions were resolved dynamically at build time vialatest.releaseand+ranges. This made builds non-reproducible — the same commit could produce different results depending on when it was built.Three sources of dynamic versions are now pinned:
library/testLibraryconfigs) —whenObjectAddedhooks use pinned version instead oflatest.releaselatestDepTestLibraryconfigs —whenObjectAddedhooks use pinned version instead of declared rangelatest.release/+range strings in ~70 individual build files — caught byresolutionStrategy.eachDependencyin the convention plugin (no individual build file changes needed)settings.gradle.kts— pinned via JSON lookupTo bypass pinning and resolve against live latest versions (e.g. for debugging):
To update the pinned versions locally:
If a dependency is not present in the JSON file, it falls back to the original dynamic version.
Why a GitHub Action instead of Renovate?
Renovate is the natural tool for dependency updates, but this file is a poor fit:
Same artifact, multiple version ranges — entries like
java-client,java-client#2.+,java-client#2.5.+coexist. Renovate identifies deps by name, so it can't handle the same Maven coordinate with different range constraints without extensive packageRules.Dynamic version constraints — the
#2.+,#4.3.+suffixes encode allowed version ranges. Renovate can't deriveallowedVersionsfrom a regex capture; you'd need explicit packageRules for each of the dozens of unique range patterns.Custom pre-release filtering — the Gradle task filters
-alpha,-beta,-rc,.m(Lettuce milestones),-nf-execution(GraphQL), git SHAs, and datetime versions. Renovate's stability filtering won't match these project-specific quirks.Gradle resolution semantics — the task resolves versions through Gradle's actual dependency resolution (respecting BOMs, platforms, transitive constraints). Renovate queries Maven Central independently, which could diverge for BOM-managed dependencies.
The GitHub Action runs Gradle's own resolution, so the pinned versions exactly match what
testLatestDepsbuilds see.Test plan
resolveLatestDepVersions— resolved 475 versions-PtestLatestDeps=true— all passed