Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ee013b9
CAMEL-23703: camel-launcher - JReleaser package generation for five p…
ammachado Jul 17, 2026
6123537
CAMEL-23703: camel-launcher - fix critical JReleaser packaging review…
ammachado Jul 19, 2026
71c9709
CAMEL-23703: camel-launcher - clarify Chocolatey ARM64 packaging docs
ammachado Jul 19, 2026
152eb27
CAMEL-23703: Address JReleaser packaging review feedback
ammachado Jul 20, 2026
43ebbbf
CAMEL-23703: Remove Windows Batch packaging wrapper
ammachado Jul 20, 2026
71450c2
CAMEL-23703: Fix WinGet launcher packaging
ammachado Jul 20, 2026
8c1d90b
CAMEL-23703: Document non-Maven WinGet payload design
ammachado Jul 20, 2026
643f789
CAMEL-23703: Plan non-Maven WinGet payload implementation
ammachado Jul 20, 2026
e2f01cf
CAMEL-23703: Keep WinGet payload out of Maven repositories
ammachado Jul 20, 2026
0206b35
CAMEL-23703: Verify reproducible WinGet packaging
ammachado Jul 20, 2026
cefb1a5
CAMEL-23703: Verify archived WinGet payload bytes
ammachado Jul 20, 2026
6f4913e
CAMEL-23703: Stage WinGet payload with Apache distributions
ammachado Jul 20, 2026
b75a7a5
CAMEL-23703: Build native executables during a real release
ammachado Jul 20, 2026
6bf28e1
CAMEL-23703: Fix packaging defects found by running JReleaser
ammachado Jul 20, 2026
d75c956
CAMEL-23703: Require the voted digest to promote the WinGet candidate
ammachado Jul 20, 2026
5dbd7ec
CAMEL-23703: Share one content definition between both launcher archives
ammachado Jul 20, 2026
0b14a76
CAMEL-23703: Align WinGet manifest schema versions
ammachado Jul 20, 2026
698ee62
CAMEL-23703: Pin the PE timestamp so camel.exe builds reproducibly
ammachado Jul 20, 2026
5fe281c
CAMEL-23703: Make the repackaged launcher JAR reproducible
ammachado Jul 20, 2026
b07942c
CAMEL-23703: Replace the placeholder repackager tests with real ones
ammachado Jul 20, 2026
cea612f
CAMEL-23703: Share one content definition between both launcher archives
ammachado Jul 20, 2026
28fede2
CAMEL-23703: Run camel-launcher-native mvn steps in batch mode
ammachado Jul 20, 2026
1f0f9e1
CAMEL-23703: Clarify why publish is unimplemented pending a PMC decision
ammachado Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 72 additions & 4 deletions .github/workflows/camel-launcher-native-exe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:
# Plain mvn: this reactor is only a handful of modules, so the mvnd daemon
# brings no parallelism benefit and adds a cold-start failure mode.
run: |
mvn -pl buildingtools,tooling/camel-exe -am verify -Dcamel.exe.build=true -DskipTests
mvn -B -pl buildingtools,tooling/camel-exe -am verify -Dcamel.exe.build=true -DskipTests
- name: Upload Windows x64 camel.exe
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down Expand Up @@ -139,7 +139,8 @@ jobs:
- name: Smoke test Windows x64 camel.exe
shell: bash
run: |
mvn -B -ntp -pl buildingtools,tooling/camel-exe -am test -Dtest=CamelExeBootstrapTest -Dsurefire.failIfNoSpecifiedTests=false
mvn -B -ntp -pl buildingtools,tooling/camel-exe -am test \
-Dtest=CamelExeBootstrapTest -Dsurefire.failIfNoSpecifiedTests=false

camel-launcher-native:
needs: detect-changes
Expand Down Expand Up @@ -174,6 +175,14 @@ jobs:
fi
echo "Launcher upstream snapshots for ${VERSION} are available."
- uses: ./.github/actions/setup-llvm-mingw
- name: Install the launcher's in-repo build plugins
# camel-repackager-maven-plugin builds the launcher's self-executing JAR. It is not in the
# -pl list below, so without this step Maven resolves it from the Apache snapshot repository
# and any change made to it in this PR is silently not exercised. Maven cannot consume a
# plugin produced by the same reactor invocation, hence the separate installation.
run: |
mvn -B -P apache-snapshots -ntp \
-pl tooling/maven/camel-repackager-maven-plugin install -DskipTests
- name: Build launcher with native camel.exe (x64 + arm64)
# No -am: build only the listed modules. The launcher's other in-repo
# dependencies (camel-jbang-core, its jbang plugins, camel-core, ...) resolve
Expand All @@ -186,12 +195,12 @@ jobs:
# daemon brings no parallelism benefit and adds a cold-start failure mode.
# -P apache-snapshots is required here so the upstream modules resolve.
run: |
mvn -P apache-snapshots \
mvn -B -P apache-snapshots \
-pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher verify \
-Dcamel.exe.build=true -DskipTests
- name: Assert archive carries both exe files
run: |
ZIP=$(find dsl/camel-jbang/camel-launcher/target -maxdepth 1 -name 'camel-launcher-*-bin.zip' -print -quit)
ZIP=$(find dsl/camel-jbang/camel-launcher/target -maxdepth 1 -name 'camel-launcher-*-winget-bin.zip' -print -quit)
echo "Checking archive: $ZIP"
ENTRIES=$(unzip -l "$ZIP" | grep -Ec '/bin/camel-(x64|arm64)\.exe$')
if [ "$ENTRIES" -ne 2 ]; then
Expand All @@ -200,3 +209,62 @@ jobs:
exit 1
fi
echo "Both bin/camel-x64.exe and bin/camel-arm64.exe found in archive"
- name: Verify reproducible native executables and WinGet archive
run: |
HASH_FILE="$RUNNER_TEMP/winget-first-build.sha256"
sha256sum \
tooling/camel-exe/target/camel-x64.exe \
tooling/camel-exe/target/camel-arm64.exe \
dsl/camel-jbang/camel-launcher/target/camel-launcher-*-winget-bin.zip \
> "$HASH_FILE"

# 'clean verify' wipes target, so keep the first-build outputs around; on a mismatch the
# differing byte offsets and zip entry listing say far more than a bare FAILED.
FIRST_BUILD="$RUNNER_TEMP/winget-first-build"
mkdir -p "$FIRST_BUILD"
cp tooling/camel-exe/target/camel-x64.exe \
tooling/camel-exe/target/camel-arm64.exe \
dsl/camel-jbang/camel-launcher/target/camel-launcher-*-winget-bin.zip "$FIRST_BUILD"

mvn -B -P apache-snapshots \
-pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher clean verify \
-Dcamel.exe.build=true -DskipTests

if ! sha256sum --check "$HASH_FILE"; then
for ARCH in x64 arm64; do
echo "=== differing bytes in camel-${ARCH}.exe (first 40) ==="
cmp -l "$FIRST_BUILD/camel-${ARCH}.exe" \
"tooling/camel-exe/target/camel-${ARCH}.exe" | head -40 || true
done
# 'unzip -v' lists each entry's CRC and timestamp, so the diff distinguishes an entry
# whose contents changed from one that merely got a new modification time.
echo "=== differing WinGet archive entries ==="
ZIP=$(find dsl/camel-jbang/camel-launcher/target -maxdepth 1 -name '*-winget-bin.zip' -print -quit)
diff <(unzip -v "$FIRST_BUILD"/*-winget-bin.zip) <(unzip -v "$ZIP") || true

# The launcher JAR is itself an archive, so a CRC change above says nothing about which of
# its thousands of entries moved. Unpack both and diff one level down.
echo "=== differing entries inside the launcher JAR ==="
unzip -qo "$FIRST_BUILD"/*-winget-bin.zip '*/bin/camel-launcher-*.jar' -d "$RUNNER_TEMP/jar-first"
unzip -qo "$ZIP" '*/bin/camel-launcher-*.jar' -d "$RUNNER_TEMP/jar-second"
diff <(unzip -v "$(find "$RUNNER_TEMP/jar-first" -name '*.jar' -print -quit)") \
<(unzip -v "$(find "$RUNNER_TEMP/jar-second" -name '*.jar' -print -quit)") \
| head -60 || true
exit 1
fi
- name: Verify WinGet archive is not deployed by Maven
run: |
MAVEN_REPO="$RUNNER_TEMP/winget-maven-repo"
mvn -B -P apache-snapshots \
-pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher deploy \
-Dcamel.exe.build=true -DskipTests \
-DaltSnapshotDeploymentRepository="winget-check::file://$MAVEN_REPO"

# Matches the ZIP and every sidecar Maven may generate alongside it
# (.sha1, .md5, .sha256, .sha512, .asc), not just the two seen locally.
DEPLOYED_WINGET_FILES=$(find "$MAVEN_REPO" -type f -name '*-winget-bin.zip*' -print)
if [ -n "$DEPLOYED_WINGET_FILES" ]; then
echo "ERROR: Maven deployed the local-only WinGet payload:"
echo "$DEPLOYED_WINGET_FILES"
exit 1
fi
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,23 @@ The Gentoo `java-config` auto-detection and the IBM AIX `$JAVA_HOME/jre/sh/java`
removed; set `JAVA_HOME` or `JAVACMD` explicitly on those platforms.
`JAVA_OPTS` handling is unchanged: when unset, the default `-Xmx512m` heap is applied.

==== Native `camel.exe` in the launcher distribution
==== Native `camel.exe` in the WinGet package

The `camel-launcher` distribution now ships two native Windows executables:
`bin/camel-x64.exe` (x86_64) and `bin/camel-arm64.exe` (aarch64), alongside
`bin/camel.bat`. Both are thin bootstraps: they forward all arguments to the
adjacent `camel.bat` (preserving spaces and Unicode) and return its exit code.
They exist so package managers that require a genuine executable (such as WinGet)
work correctly. Users may continue to invoke `bin\camel.bat`; all three behave
identically.
The WinGet package now ships two native Windows executables: `bin/camel-x64.exe`
(x86_64) and `bin/camel-arm64.exe` (aarch64), alongside `bin/camel.bat`. Both are
thin bootstraps: they forward all arguments to the adjacent `camel.bat` (preserving
spaces and Unicode) and return its exit code. WinGet selects the executable matching
the host architecture and exposes it as `camel.exe`.

The public `camel-launcher-<version>-bin.zip` and `.tar.gz` archives do not contain
these WinGet-specific executables. Windows users of those archives should continue
to invoke `bin\camel.bat`.

The native executables are now cross-compiled from any host OS using
https://github.com/mstorsjo/llvm-mingw[clang/llvm-mingw], replacing the previous
MSVC-only build that required a Windows host. The build is activated by
`-Dcamel.exe.build=true` or `-Prelease` and requires `llvm-mingw` on `PATH`.

If you have tooling that referenced `bin/camel.exe`, update it to use
`bin/camel-x64.exe` (for x86_64 Windows) or `bin/camel-arm64.exe` (for ARM64
Windows).
`-Dcamel.exe.build=true` and requires `llvm-mingw` on `PATH`. Release builds get this
from `maven-release-plugin`, which passes it alongside `-Prelease`.

==== OpenTelemetry agent export target changes

Expand Down Expand Up @@ -154,7 +153,7 @@ Installation is always per-user and never requires elevation or `sudo`:
prints guidance instead.
* Windows (`install.ps1`) installs under `%LOCALAPPDATA%\Apache Camel\cli\versions\<version>` and
activates it via a `camel.cmd` shim at `%LOCALAPPDATA%\Apache Camel\bin\camel.cmd` that delegates
to the staged `camel-x64.exe` or `camel-arm64.exe` (auto-detected). The bin directory is added
to the staged `camel.bat`. The bin directory is added
once, case-insensitively, to the current user's `PATH`; the machine `PATH` is never modified.

Previously installed version directories are left in place after an upgrade or downgrade and
Expand Down
36 changes: 35 additions & 1 deletion docs/user-manual/modules/ROOT/pages/release-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ Complete the following steps to create a new Camel release:

$ ./mvnw release:perform -Prelease

. Stage the WinGet payload in the Apache development distribution repository:

* `release:perform` builds the non-attached WinGet ZIP in its release checkout. Prepare the
candidate with the same candidate number used in the vote:

cd ${CAMEL_ROOT_DIR}/etc/scripts
./stage-winget-distro.sh <Camel version> <candidate number> \
${CAMEL_ROOT_DIR}/target/checkout/dsl/camel-jbang/camel-launcher/target/camel-launcher-<Camel version>-winget-bin.zip

* Review the printed `svn status`, commit the working copy only after checking the ZIP, `.asc`,
and `.sha512`, and include the resulting `dist/dev` candidate URL together with the Nexus staging
repository URL in the vote email. The WinGet ZIP is not present in the Nexus staging repository.

* The script prints the candidate's SHA-512 digest. **Include it in the vote email**: promotion
requires it, because the candidate number on its own cannot distinguish the approved candidate from
a superseded one (each RC directory carries its own self-consistent `.sha512` and `.asc`).


. Close the Apache staging repository:

Expand Down Expand Up @@ -385,7 +402,24 @@ This will release the artifacts.
. Copy distribution to Apache website:

cd ${CAMEL_ROOT_DIR}/etc/scripts
./release-distro.sh <Camel version>
./release-distro.sh <Camel version> <temp-directory> <WinGet candidate number> <WinGet SHA-512>

* The script exports the voted WinGet ZIP, signature, and SHA-512 checksum from `dist/dev`, verifies
them, and adds the exact files to the local `dist/release` working copy. It does not rebuild the ZIP.
Review and commit the combined working copy using the commands printed by the script.

* `<WinGet SHA-512>` is the digest from the vote email. Promotion aborts if the exported candidate
does not match it, which is what stops a superseded RC from being promoted by mistake.

* After the release is available from
`https://archive.apache.org/dist/camel/apache-camel/<Camel version>/`, run the JReleaser package
preparation. WinGet preparation fails until the archived ZIP is available and byte-identical to the
approved local ZIP.

* Remove the promoted candidate after the release copy is committed:

svn rm https://dist.apache.org/repos/dist/dev/camel/apache-camel/<Camel version>-rc<WinGet candidate number> \
-m "Remove promoted Apache Camel <Camel version> WinGet candidate"

. Copy SBOMs to Apache website:

Expand Down
59 changes: 52 additions & 7 deletions dsl/camel-jbang/camel-launcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ This will create:
1. A self-executing JAR (`camel-launcher-<version>.jar`) in the `target` directory using Spring Boot's nested JAR structure
2. Distribution archives (`camel-launcher-<version>-bin.zip` and `camel-launcher-<version>-bin.tar.gz`) in the `target` directory

When `-Dcamel.exe.build=true` is enabled, the distribution archives also include native Windows bootstraps built by
When `-Dcamel.exe.build=true` is enabled, Maven also creates a WinGet-only archive,
`camel-launcher-<version>-winget-bin.zip`, containing the native Windows bootstraps built by
[`tooling/camel-exe`](../../../tooling/camel-exe): `bin/camel-x64.exe` and `bin/camel-arm64.exe`.
The assembly uses `attach=false`, so the ZIP remains in this module's `target` directory for the
Apache distribution release flow and is not installed or deployed to a Maven repository.
Release builds use llvm-mingw to cross-compile both executables on Linux. The launcher module verifies during `verify`
that both files are staged and present in the assembled ZIP:
that both files are staged, absent from the public archives, and present in the WinGet ZIP:

```bash
mvn -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify -Dcamel.exe.build=true
```

To build and test only the native bootstrap:
To build and test only the native bootstraps:

```bash
mvn -pl buildingtools,tooling/camel-exe -am verify -Dcamel.exe.build=true
Expand Down Expand Up @@ -65,12 +68,10 @@ java -jar camel-launcher-<version>.jar run hello.java

# On Windows
bin\camel.bat [command] [options]
bin\camel-x64.exe [command] [options]
bin\camel-arm64.exe [command] [options]
```

The native executables and `camel.bat` behave identically on Windows; the native executables exist for package managers
(such as WinGet) that require a genuine executable command.
The native executables are confined to the WinGet-only archive. WinGet selects the executable matching the host
architecture and exposes it as `camel.exe`.

## Benefits

Expand Down Expand Up @@ -107,3 +108,47 @@ camel-launcher-<version>.jar
```

This structure provides better performance and reliability compared to traditional fat JARs where all classes are merged together.

## Packaging (JReleaser)

The `jreleaser.yml` in this module configures distribution packaging for Homebrew, SDKMAN,
WinGet, Scoop, and Chocolatey. Custom templates live under
`src/jreleaser/distributions/camel-cli/<packager>/`.

### Homebrew Maven Central URL convention

Homebrew's `FormulaAudit::Urls` rubocop
([source](https://docs.brew.sh/rubydoc/RuboCop/Cop/UrlHelper.html)) requires Maven Central
artifacts to use the `search.maven.org` redirector URL instead of `repo1.maven.org`:

```
# Required by Homebrew:
https://search.maven.org/remotecontent?filepath=org/apache/camel/camel-launcher/VERSION/FILE

# NOT accepted (triggers brew audit failure):
https://repo1.maven.org/maven2/org/apache/camel/camel-launcher/VERSION/FILE
```

Both resolve to the same artifact — `search.maven.org/remotecontent` simply redirects to
`repo1.maven.org/maven2`. This rule applies **only to Homebrew**. SDKMAN, Scoop, and Chocolatey
use `repo1.maven.org` directly. WinGet uses the immutable Apache archive URL because its dedicated
ZIP is an Apache distribution payload and is not published to Maven Central.

Because that redirector URL carries a query string, the templates render every URL with Mustache's
triple-brace form (`{{{distributionUrl}}}`). JReleaser HTML-escapes the ordinary double-brace form,
which rewrites the `=` in `?filepath=` as `&#61;` and produces a URL Homebrew cannot download. The
other packagers use path-only URLs today and would not notice the difference, but they use the
unescaped form too so that adding a query parameter later cannot silently break them.

### Native bootstrap executables

The dedicated `camel-launcher-<version>-winget-bin.zip` ships `camel-x64.exe` and
`camel-arm64.exe` for WinGet, which requires a genuine portable executable per architecture
(see the WinGet `installer.yaml.tpl` override). The public ZIP and TAR archives do not contain
these WinGet-specific executables.

Before JReleaser prepares the WinGet manifest, `camel-package.sh` downloads the versioned Apache
archive URL and fails unless its bytes exactly match the local WinGet ZIP.

Chocolatey and Scoop use `camel.bat` as their entry point instead (it needs only a JRE, so it is
architecture-neutral and requires no per-architecture binary).
Loading