Skip to content

Make the build reproducible by deriving version metadata from the manifest - #489

Merged
ppkarwasz merged 8 commits into
trunkfrom
reproducible-version-info
Aug 25, 2026
Merged

Make the build reproducible by deriving version metadata from the manifest#489
ppkarwasz merged 8 commits into
trunkfrom
reproducible-version-info

Conversation

@ppkarwasz

Copy link
Copy Markdown
Member

VersionInfo was the last part of the build that could not produce the same output twice.

saveVersion.sh (and its PowerShell twin) generated a @VersionAnnotation at generate-sources recording the builder's user name, host name, working directory, wall clock and an MD5 of every *.java file in the tree. Six of the seven fields were therefore machine-specific, and the source distribution generated by logging-parent carries 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 in target/classes as well as the JAR, so the values are also available to tests and in the IDE:

Header Source Specified by
Bundle-SCM <scm> OSGi Core R8, section 3.2.1
Implementation-Timestamp project.build.outputTimestamp nothing, see below
Purl groupId, artifactId, version the value follows the package-url specification, the header name is ours

Neither 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-Id was considered for the latter and rejected: it is deprecated in java.util.jar.Attributes.Name as a leftover of the extension mechanism removed by JEP 220.

VersionInfo

Method Source
getVersion() Implementation-Version, then pom.properties
getPurl() (new) Purl, then built from pom.properties
getUrl() Bundle-SCM attribute url
getBranch() Bundle-SCM attribute tag
getDate() Implementation-Timestamp
getRevision() fixed, until the build records a commit id again
getUser(), getSrcChecksum() fixed, deprecated for removal

The manifest is resolved against the location of VersionInfo.class rather than looked up on the class path, so another artifact cannot answer, and it is accepted only when its Purl identifies flume-ng-core. When Flume has been shaded into an uber JAR the manifest belongs to the shading project, so it is rejected and META-INF/maven/org.apache.flume/flume-ng-core/pom.properties is used instead.

VersionAnnotation, both saveVersion scripts 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 the revision and project.build.outputTimestamp conventions 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

  • Two clean builds of flume-ng-core produce the same SHA-256.
  • flume-ng version prints identical output from the JAR and from target/classes.
  • The shaded fallback is exercised against a real uber JAR built with a foreign manifest.
  • mvn verify is green; PMD and SpotBugs counts are unchanged.

Still open

The release workflow in logging-parent bumps revision and project.build.outputTimestamp in the root POM only, so flume-parent will go stale at release time. That needs a fix upstream.

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
@ppkarwasz
ppkarwasz requested review from rgoers and a balanced review from Copilot August 21, 2026 10:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 in VersionInfo.
  • Standardize module versions on ${revision} and update flume-parent properties (incl. project.build.outputTimestamp) for reproducible output.
  • Expand VersionInfo unit 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.

Comment thread flume-ng-core/src/main/java/org/apache/flume/tools/VersionInfo.java
Comment thread flume-ng-core/src/main/java/org/apache/flume/tools/VersionInfo.java
Comment thread flume-ng-core/src/test/java/org/apache/flume/tools/TestVersionInfo.java Outdated
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
@ppkarwasz
ppkarwasz enabled auto-merge (squash) August 25, 2026 11:32
@ppkarwasz
ppkarwasz merged commit c0d73dc into trunk Aug 25, 2026
11 checks passed
@ppkarwasz
ppkarwasz deleted the reproducible-version-info branch August 25, 2026 11:48
@ppkarwasz

Copy link
Copy Markdown
Member Author

I merged this to fix the reproducibility check, but I am open to follow-up PRs if this requires refinement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants