Make the build reproducible by deriving version metadata from the manifest - #489
Conversation
Every published module inherits from `flume-parent`, not from the aggregator, so the CI-friendly properties have to be declared there as well. Without them the release automation bumps only the aggregator and the artifacts ship a stale version and build timestamp. Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QwMh1JvFaPBgWEGrMgMj7
`saveVersion.sh` recorded the builder's user name, host, clock and a checksum of the working copy, so no two builds agreed and the source distribution, which carries no repository metadata, agreed with none. The same facts now come from POM-derived manifest headers, which a Git checkout and the source archive produce identically. `getUser()` and `getSrcChecksum()` are deprecated for removal, and `getRevision()` reports nothing until the build records a commit id again. Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QwMh1JvFaPBgWEGrMgMj7
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Makes Flume builds reproducible by removing machine-specific version annotation generation and instead deriving build/version metadata from Maven/BND manifest headers (with Maven descriptor fallback).
Changes:
- Replace
@VersionAnnotation+saveVersion.*scripts with manifest/pom.properties based metadata lookup inVersionInfo. - Standardize module versions on
${revision}and updateflume-parentproperties (incl.project.build.outputTimestamp) for reproducible output. - Expand
VersionInfounit tests to cover manifest preference, fallbacks, and SCM header parsing.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| flume-tools/pom.xml | Switches parent version to ${revision} for CI-friendly versioning. |
| flume-parent/pom.xml | Introduces ${revision}, updates SCM tag, sets reproducible timestamp, and appends manifest headers via bnd-extra-config. |
| flume-ng-sources/pom.xml | Switches parent version to ${revision}. |
| flume-ng-sources/flume-taildir-source/pom.xml | Switches parent version to ${revision}. |
| flume-ng-sources/flume-syslog-source/pom.xml | Switches parent version to ${revision}. |
| flume-ng-sources/flume-netcat-source/pom.xml | Switches parent version to ${revision}. |
| flume-ng-sources/flume-http-source/pom.xml | Switches parent version to ${revision}. |
| flume-ng-sdk/pom.xml | Switches parent version to ${revision} and customizes test-jar manifest to avoid inheriting the main bundle’s manifest metadata. |
| flume-ng-node/pom.xml | Switches parent version to ${revision}. |
| flume-ng-instrumentation/pom.xml | Switches parent version to ${revision}. |
| flume-ng-instrumentation/flume-prometheus-monitor/pom.xml | Switches parent version to ${revision}. |
| flume-ng-instrumentation/flume-http-monitor/pom.xml | Switches parent version to ${revision}. |
| flume-ng-instrumentation/flume-ganglia-monitor/pom.xml | Switches parent version to ${revision}. |
| flume-ng-dist/pom.xml | Switches parent version to ${revision}. |
| flume-ng-core/src/test/java/org/apache/flume/tools/TestVersionInfo.java | Reworks tests to validate new manifest/pom.properties metadata resolution and SCM header parsing. |
| flume-ng-core/src/main/java/org/apache/flume/tools/VersionInfo.java | Reimplements version metadata resolution via manifest headers (with pom.properties fallback) and removes dependency on generated package annotation. |
| flume-ng-core/src/main/java/org/apache/flume/VersionAnnotation.java | Removes the now-unneeded build-time annotation API. |
| flume-ng-core/scripts/saveVersion.sh | Removes unreproducible, machine-specific version generation script. |
| flume-ng-core/scripts/saveVersion.ps1 | Removes unreproducible, machine-specific version generation script. |
| flume-ng-core/pom.xml | Removes OS-conditional profiles that generated VersionAnnotation sources; switches parent version to ${revision}. |
| flume-ng-configuration/pom.xml | Switches parent version to ${revision}. |
| flume-ng-configfilters/pom.xml | Switches parent version to ${revision}. |
| flume-ng-configfilters/flume-ng-external-process-config-filter/pom.xml | Switches parent version to ${revision}. |
| flume-ng-configfilters/flume-ng-environment-variable-config-filter/pom.xml | Switches parent version to ${revision}. |
| flume-ng-configfilters/flume-ng-config-filter-api/pom.xml | Switches parent version to ${revision}. |
| flume-ng-channels/pom.xml | Switches parent version to ${revision}. |
| flume-ng-channels/flume-spillable-memory-channel/pom.xml | Switches parent version to ${revision}. |
| flume-ng-channels/flume-file-channel/pom.xml | Switches parent version to ${revision}. |
| flume-ng-auth/pom.xml | Switches parent version to ${revision}. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A present but empty header would have suppressed the fallback. Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zsFn1f2B32vcNoCeZSfn7
The `@return` clause promised ISO-8601 unconditionally. Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zsFn1f2B32vcNoCeZSfn7
Downstream builds override `<scm>`; assert only invariants, while still rejecting tags that betray an unset `<scm><tag>`. Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zsFn1f2B32vcNoCeZSfn7
Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zsFn1f2B32vcNoCeZSfn7
Fixed sleeps lose to PowerShell start-up times on loaded CI runners: await the source counter instead. `Get-Content -Wait` keeps the process alive like `tail -f`, so the batch timeout, not the EOF flush, delivers the events. Writing the input file before the source starts removes the race with the one-shot read of the old command. Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zsFn1f2B32vcNoCeZSfn7
The change is unrelated to this PR; it moves to its own PR from trunk. This reverts commit 9deaa619830cd845e91a1855076ac53c99cbabc0. Assisted-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zsFn1f2B32vcNoCeZSfn7
|
I merged this to fix the reproducibility check, but I am open to follow-up PRs if this requires refinement. |
VersionInfowas the last part of the build that could not produce the same output twice.saveVersion.sh(and its PowerShell twin) generated a@VersionAnnotationatgenerate-sourcesrecording the builder's user name, host name, working directory, wall clock and an MD5 of every*.javafile in the tree. Six of the seven fields were therefore machine-specific, and the source distribution generated bylogging-parentcarries no repository metadata at all, so a build from it could never match a build from a Git checkout.The same facts now come from manifest headers derived from the POM, so a Git checkout and the source archive produce byte-identical artifacts.
Manifest
Three headers are appended to the BND-generated manifest through
bnd-extra-config, which puts them intarget/classesas well as the JAR, so the values are also available to tests and in the IDE:Bundle-SCM<scm>Implementation-Timestampproject.build.outputTimestampPurlgroupId,artifactId,versionNeither the JAR File Specification nor OSGi defines a header for a build timestamp or a package identifier, so those two are Flume-specific.
Implementation-Vendor-Idwas considered for the latter and rejected: it is deprecated injava.util.jar.Attributes.Nameas a leftover of the extension mechanism removed by JEP 220.VersionInfogetVersion()Implementation-Version, thenpom.propertiesgetPurl()(new)Purl, then built frompom.propertiesgetUrl()Bundle-SCMattributeurlgetBranch()Bundle-SCMattributetaggetDate()Implementation-TimestampgetRevision()getUser(),getSrcChecksum()The manifest is resolved against the location of
VersionInfo.classrather than looked up on the class path, so another artifact cannot answer, and it is accepted only when itsPurlidentifiesflume-ng-core. When Flume has been shaded into an uber JAR the manifest belongs to the shading project, so it is rejected andMETA-INF/maven/org.apache.flume/flume-ng-core/pom.propertiesis used instead.VersionAnnotation, bothsaveVersionscripts and the two OS-conditional profiles that ran them are removed.Also fixed
flume-parent, which is the parent of every published module, never received therevisionandproject.build.outputTimestampconventions applied to the aggregator in #467. It still carried a 2022 timestamp and a hard-coded version, and since the release automation patches only the root POM, neither would have been updated at release time. The first commit applies the convention there and switches all child modules to${revision}.Verification
flume-ng-coreproduce the same SHA-256.flume-ng versionprints identical output from the JAR and fromtarget/classes.mvn verifyis green; PMD and SpotBugs counts are unchanged.Still open
The release workflow in
logging-parentbumpsrevisionandproject.build.outputTimestampin the root POM only, soflume-parentwill go stale at release time. That needs a fix upstream.