From 395ba67e1fa9f6c27b21caee9533243488a4d45f Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:53:18 -0400 Subject: [PATCH 1/5] CAMEL-23703: camel-launcher - cross-compile native camel.exe for x64 and arm64 via llvm-mingw Replace the MSVC/Windows-only native exe build with a host-OS-independent cross-compile using clang from the llvm-mingw toolchain (pinned, SHA256-verified). Produces bin/camel-x64.exe and bin/camel-arm64.exe from any host OS, activated by -Dcamel.exe.build=true or -Prelease. Co-Authored-By: Claude Sonnet 5 Co-Authored-By: Claude Opus 4.8 --- .github/actions/setup-llvm-mingw/action.yml | 38 +++++++ ...dows.yml => camel-launcher-native-exe.yml} | 97 +++++++++++------ Jenkinsfile.deploy | 24 ++++- .../pages/camel-4x-upgrade-guide-4_22.adoc | 21 +++- .../modules/ROOT/pages/release-guide.adoc | 5 + dsl/camel-jbang/camel-launcher/README.md | 19 ++-- dsl/camel-jbang/camel-launcher/pom.xml | 60 ++++++++--- .../camel-launcher/src/main/assembly/bin.xml | 3 +- ...eIT.java => CamelLauncherNativeExeIT.java} | 45 ++++---- pom.xml | 1 + tooling/camel-exe/README.md | 31 +++--- tooling/camel-exe/pom.xml | 101 +++++++++++++----- tooling/camel-exe/src/main/native/README.md | 55 +++++++--- .../tooling/exe/CamelExeBootstrapTest.java | 12 ++- .../org/apache/camel/maven/RepackageMojo.java | 3 +- .../apache/camel/maven/RepackageMojoTest.java | 3 +- 16 files changed, 375 insertions(+), 143 deletions(-) create mode 100644 .github/actions/setup-llvm-mingw/action.yml rename .github/workflows/{camel-launcher-windows.yml => camel-launcher-native-exe.yml} (51%) rename dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/{CamelLauncherWindowsExeIT.java => CamelLauncherNativeExeIT.java} (56%) diff --git a/.github/actions/setup-llvm-mingw/action.yml b/.github/actions/setup-llvm-mingw/action.yml new file mode 100644 index 0000000000000..397d697656f63 --- /dev/null +++ b/.github/actions/setup-llvm-mingw/action.yml @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: 'setup-llvm-mingw' +description: >- + Installs a pinned release of llvm-mingw on ubuntu-latest, providing the + x86_64-w64-mingw32-clang and aarch64-w64-mingw32-clang cross-compilers + used to build camel-x64.exe and camel-arm64.exe. + +runs: + using: "composite" + steps: + - run: | + VERSION='20260616' + SHA256='534b92e067b22a6b4441f48ae9240a3341b17825d04d577eab0cf85c44b4deda' + TARBALL="llvm-mingw-${VERSION}-ucrt-ubuntu-22.04-x86_64.tar.xz" + INSTALL_DIR="${RUNNER_TEMP}/llvm-mingw" + mkdir -p "$INSTALL_DIR" + curl -fsSL -o "$TARBALL" \ + "https://github.com/mstorsjo/llvm-mingw/releases/download/${VERSION}/${TARBALL}" + echo "${SHA256} ${TARBALL}" | sha256sum -c - + tar -xf "$TARBALL" -C "$INSTALL_DIR" + echo "${INSTALL_DIR}/llvm-mingw-${VERSION}-ucrt-ubuntu-22.04-x86_64/bin" >> "$GITHUB_PATH" + shell: bash diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-native-exe.yml similarity index 51% rename from .github/workflows/camel-launcher-windows.yml rename to .github/workflows/camel-launcher-native-exe.yml index f4032f6ce1086..2c3e1be5b9cfc 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-native-exe.yml @@ -15,7 +15,10 @@ # limitations under the License. # -name: Camel launcher Windows build +# Cross-compiles camel.exe (x64 + arm64) on Linux using clang/llvm-mingw. +# Transitional safety net, remove once a real release has exercised the +# -Prelease auto-trigger path successfully. +name: Camel launcher native exe build on: push: @@ -24,14 +27,16 @@ on: paths: - 'tooling/camel-exe/**' - 'dsl/camel-jbang/camel-launcher/**' - - '.github/workflows/camel-launcher-windows.yml' + - '.github/actions/setup-llvm-mingw/**' + - '.github/workflows/camel-launcher-native-exe.yml' pull_request: branches: - main paths: - 'tooling/camel-exe/**' - 'dsl/camel-jbang/camel-launcher/**' - - '.github/workflows/camel-launcher-windows.yml' + - '.github/actions/setup-llvm-mingw/**' + - '.github/workflows/camel-launcher-native-exe.yml' workflow_dispatch: concurrency: @@ -72,11 +77,11 @@ jobs: camel_exe=false camel_launcher=false - if echo "$CHANGED" | grep -qE '^(tooling/camel-exe/|\.github/workflows/camel-launcher-windows\.yml)'; then + if echo "$CHANGED" | grep -qE '^(tooling/camel-exe/|\.github/actions/setup-llvm-mingw/|\.github/workflows/camel-launcher-native-exe\.yml)'; then camel_exe=true fi - if echo "$CHANGED" | grep -qE '^(dsl/camel-jbang/camel-launcher/|tooling/camel-exe/|\.github/workflows/camel-launcher-windows\.yml)'; then + if echo "$CHANGED" | grep -qE '^(dsl/camel-jbang/camel-launcher/|tooling/camel-exe/|\.github/actions/setup-llvm-mingw/|\.github/workflows/camel-launcher-native-exe\.yml)'; then camel_launcher=true fi @@ -86,7 +91,36 @@ jobs: camel-exe: needs: detect-changes if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-exe == 'true' - runs-on: windows-2022 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Set up JDK 21 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + cache: 'maven' + - id: install-mvnd + uses: ./.github/actions/install-mvnd + - uses: ./.github/actions/setup-llvm-mingw + - name: Build and test native camel.exe (x64 + arm64) + run: | + ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd $MVND_OPTS -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: + name: camel-x64-exe + path: tooling/camel-exe/target/camel-x64.exe + if-no-files-found: error + + camel-exe-windows-smoke: + needs: + - detect-changes + - camel-exe + if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-exe == 'true' + runs-on: windows-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: @@ -97,18 +131,20 @@ jobs: distribution: 'temurin' java-version: '21' cache: 'maven' - - name: Build and test native camel.exe - shell: cmd + - name: Download Windows x64 camel.exe + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: camel-x64-exe + path: tooling/camel-exe/target + - name: Smoke test Windows x64 camel.exe + shell: bash run: | - for /f "usebackq delims=" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i" - call "%VSINSTALL%\VC\Auxiliary\Build\vcvars64.bat" - mvn -B -ntp -pl buildingtools install -DskipTests - mvn -B -ntp -pl tooling/camel-exe verify -Dcamel.exe.requireWindowsExe=true + mvn -B -ntp -pl buildingtools,tooling/camel-exe -am test -Dtest=CamelExeBootstrapTest -Dsurefire.failIfNoSpecifiedTests=false - camel-launcher-windows: + camel-launcher-native: needs: detect-changes if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-launcher == 'true' - runs-on: windows-2022 + runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: @@ -119,21 +155,22 @@ jobs: distribution: 'temurin' java-version: '21' cache: 'maven' - - name: Build launcher with native camel.exe - shell: cmd + - id: install-mvnd + uses: ./.github/actions/install-mvnd + - uses: ./.github/actions/setup-llvm-mingw + - name: Build launcher with native camel.exe (x64 + arm64) run: | - for /f "usebackq delims=" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i" - call "%VSINSTALL%\VC\Auxiliary\Build\vcvars64.bat" - mvn -B -ntp -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -DskipTests -Dcamel.launcher.requireWindowsExe=true - - name: Assert archive carries bin/camel.exe - shell: pwsh + ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd $MVND_OPTS \ + -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify \ + -Dcamel.exe.build=true -DskipTests + - name: Assert archive carries both exe files run: | - $zip = Get-ChildItem dsl/camel-jbang/camel-launcher/target/camel-launcher-*-bin.zip | Select-Object -First 1 - Add-Type -AssemblyName System.IO.Compression.FileSystem - $archive = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName) - try { - $names = $archive.Entries.FullName - if (-not ($names -like '*/bin/camel.exe')) { throw 'camel.exe missing from launcher archive' } - } finally { - $archive.Dispose() - } + ZIP=$(find dsl/camel-jbang/camel-launcher/target -maxdepth 1 -name 'camel-launcher-*-bin.zip' -print -quit) + echo "Checking archive: $ZIP" + ENTRIES=$(unzip -l "$ZIP" | grep -Ec '/bin/camel-(x64|arm64)\.exe$') + if [ "$ENTRIES" -ne 2 ]; then + echo "ERROR: expected both bin/camel-x64.exe and bin/camel-arm64.exe in archive" + unzip -l "$ZIP" | grep -i exe + exit 1 + fi + echo "Both bin/camel-x64.exe and bin/camel-arm64.exe found in archive" diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy index 4d588e76bd236..4e81511a99583 100644 --- a/Jenkinsfile.deploy +++ b/Jenkinsfile.deploy @@ -17,7 +17,7 @@ def AGENT_LABEL = env.AGENT_LABEL ?: 'ubuntu' def JDK_NAME = env.JDK_NAME ?: 'jdk_25_latest' -def MAVEN_PARAMS = "-U -B -e -fae -V -Dnoassembly " +def MAVEN_PARAMS = "-U -B -e -fae -V -Dnoassembly -Dcamel.exe.build=true " pipeline { @@ -55,6 +55,27 @@ pipeline { } } + stage('Verify llvm-mingw') { + when { + anyOf { + branch 'main' + branch 'camel-4.18.x' + branch 'camel-4.14.x' + } + } + steps { + sh ''' + for compiler in x86_64-w64-mingw32-clang aarch64-w64-mingw32-clang; do + if ! command -v "$compiler" >/dev/null 2>&1; then + echo "ERROR: $compiler not found on PATH." + echo "Install llvm-mingw per docs/user-manual/modules/ROOT/pages/release-guide.adoc" + exit 1 + fi + done + ''' + } + } + stage('Build & Deploy') { when { anyOf { @@ -89,4 +110,3 @@ pipeline { } } } - diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc index 1f7a44a8662e2..24373786c303e 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc @@ -85,11 +85,22 @@ removed; set `JAVA_HOME` or `JAVACMD` explicitly on those platforms. ==== Native `camel.exe` in the launcher distribution -The `camel-launcher` Windows distribution now ships a native x64 `bin/camel.exe` -alongside `bin/camel.bat`. `camel.exe` is a thin bootstrap: it forwards all -arguments to the adjacent `camel.bat` (preserving spaces and Unicode) and returns -its exit code. It exists so package managers that require a genuine executable -users may continue to invoke `bin\camel.bat`; both behave identically. +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 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). ==== OpenTelemetry agent export target changes diff --git a/docs/user-manual/modules/ROOT/pages/release-guide.adoc b/docs/user-manual/modules/ROOT/pages/release-guide.adoc index fa0df09136c41..36c1d59c333f4 100644 --- a/docs/user-manual/modules/ROOT/pages/release-guide.adoc +++ b/docs/user-manual/modules/ROOT/pages/release-guide.adoc @@ -14,6 +14,11 @@ ApacheCon, for instance). * Make sure you have the correct maven configuration in `~/.m2/settings.xml`. * You may want to get familiar with the release settings in the parent Apache POM. * Make sure you are using the expected supported Java version. +* Install https://github.com/mstorsjo/llvm-mingw/releases/tag/20260616[llvm-mingw 20260616] and +add its `bin/` directory to `PATH`. This provides the `x86_64-w64-mingw32-clang` and +`aarch64-w64-mingw32-clang` cross-compilers used to build the native `camel-x64.exe` and +`camel-arm64.exe` Windows bootstraps. The `-Prelease` Maven profile activates the cross-compile +automatically; a missing toolchain causes a loud build failure (never a silent skip). [[ReleaseGuide-GPG]] == GPG setup diff --git a/dsl/camel-jbang/camel-launcher/README.md b/dsl/camel-jbang/camel-launcher/README.md index 845c8b894fe5a..f2d1bde1bd78b 100644 --- a/dsl/camel-jbang/camel-launcher/README.md +++ b/dsl/camel-jbang/camel-launcher/README.md @@ -16,21 +16,23 @@ This will create: 1. A self-executing JAR (`camel-launcher-.jar`) in the `target` directory using Spring Boot's nested JAR structure 2. Distribution archives (`camel-launcher--bin.zip` and `camel-launcher--bin.tar.gz`) in the `target` directory -On Windows, the distribution archives also include `bin/camel.exe`, a native bootstrap built by -[`tooling/camel-exe`](../../../tooling/camel-exe). Release builds on a Windows x64 host run an integration test during `verify` that asserts -`target/camel.exe` is staged and `bin/camel.exe` is present in the assembled ZIP: +When `-Dcamel.exe.build=true` is enabled, the distribution archives also include native Windows bootstraps built by +[`tooling/camel-exe`](../../../tooling/camel-exe): `bin/camel-x64.exe` and `bin/camel-arm64.exe`. +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: ```bash -mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify -Dcamel.launcher.requireWindowsExe=true +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: ```bash -mvn -pl tooling/camel-exe verify -Dcamel.exe.requireWindowsExe=true +mvn -pl buildingtools,tooling/camel-exe -am verify -Dcamel.exe.build=true ``` -See [`tooling/camel-exe/src/main/native/README.md`](../../../tooling/camel-exe/src/main/native/README.md) for MSVC requirements. +See [`tooling/camel-exe/src/main/native/README.md`](../../../tooling/camel-exe/src/main/native/README.md) for +llvm-mingw requirements. ## Usage @@ -63,10 +65,11 @@ java -jar camel-launcher-.jar run hello.java # On Windows bin\camel.bat [command] [options] - bin\camel.exe [command] [options] + bin\camel-x64.exe [command] [options] + bin\camel-arm64.exe [command] [options] ``` - `camel.exe` and `camel.bat` behave identically on Windows; `camel.exe` exists for package managers + 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. ## Benefits diff --git a/dsl/camel-jbang/camel-launcher/pom.xml b/dsl/camel-jbang/camel-launcher/pom.xml index fc919a9c733ed..b0490c9fcf8ca 100644 --- a/dsl/camel-jbang/camel-launcher/pom.xml +++ b/dsl/camel-jbang/camel-launcher/pom.xml @@ -334,15 +334,17 @@ include-camel-exe - - windows - + + camel.exe.build + true + @@ -350,6 +352,14 @@ camel-exe ${project.version} exe + x64 + + + org.apache.camel + camel-exe + ${project.version} + exe + arm64 @@ -359,7 +369,27 @@ maven-dependency-plugin - copy-camel-exe + copy-camel-exe-x64 + generate-resources + + copy + + + + + org.apache.camel + camel-exe + ${project.version} + exe + x64 + ${project.build.directory} + camel-x64.exe + + + + + + copy-camel-exe-arm64 generate-resources copy @@ -371,8 +401,9 @@ camel-exe ${project.version} exe + arm64 ${project.build.directory} - camel.exe + camel-arm64.exe @@ -384,15 +415,15 @@ - require-windows-exe + require-native-exe - camel.launcher.requireWindowsExe + camel.exe.build true @@ -403,7 +434,7 @@ maven-enforcer-plugin - require-camel-exe + require-native-exe-present prepare-package enforce @@ -412,7 +443,8 @@ - ${project.build.directory}/camel.exe + ${project.build.directory}/camel-x64.exe + ${project.build.directory}/camel-arm64.exe diff --git a/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml b/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml index 7506708d80a9f..61fd0d4fab820 100644 --- a/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml +++ b/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml @@ -74,7 +74,8 @@ ${project.build.directory} bin - camel.exe + camel-x64.exe + camel-arm64.exe 0755 diff --git a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherWindowsExeIT.java b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherNativeExeIT.java similarity index 56% rename from dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherWindowsExeIT.java rename to dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherNativeExeIT.java index c2108f78fa20c..bba13ac01a422 100644 --- a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherWindowsExeIT.java +++ b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherNativeExeIT.java @@ -26,43 +26,52 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** - * Release gate for the native {@code camel.exe} packaged into the launcher distribution. Runs during {@code verify} - * when {@code -Dcamel.launcher.requireWindowsExe=true} is set on a Windows x64 host. + * Release gate for the native exe files (x64 + arm64) packaged into the launcher distribution. Runs during + * {@code verify} when {@code -Dcamel.exe.build=true} is set. Cross-compiled from any host OS via clang/llvm-mingw. */ -@EnabledOnOs(OS.WINDOWS) -@EnabledIfSystemProperty(named = "camel.launcher.requireWindowsExe", matches = "true") -class CamelLauncherWindowsExeIT { +@EnabledIfSystemProperty(named = "camel.exe.build", matches = "true") +class CamelLauncherNativeExeIT { private static final Path TARGET = Paths.get("target"); - private static final Path STAGED_EXE = TARGET.resolve("camel.exe"); + private static final Path STAGED_X64 = TARGET.resolve("camel-x64.exe"); + private static final Path STAGED_ARM64 = TARGET.resolve("camel-arm64.exe"); @Test - void stagedCamelExeExists() { - assertTrue(Files.isRegularFile(STAGED_EXE), - "target/camel.exe must be present before packaging the launcher distribution"); + void stagedCamelExeX64Exists() { + assertTrue(Files.isRegularFile(STAGED_X64), + "target/camel-x64.exe must be present before packaging the launcher distribution"); } @Test - void binArchiveIncludesCamelExe() throws IOException { + void stagedCamelExeArm64Exists() { + assertTrue(Files.isRegularFile(STAGED_ARM64), + "target/camel-arm64.exe must be present before packaging the launcher distribution"); + } + + @Test + void binArchiveIncludesBothExeFiles() throws IOException { Path zip = findBinZip(); assertNotNull(zip, "camel-launcher-*-bin.zip must be produced by the assembly plugin"); try (ZipFile archive = new ZipFile(zip.toFile())) { - // maven-assembly-plugin defaults includeBaseDirectory to true, so entries are - // nested under camel-launcher-/, not at the archive root. - ZipEntry entry = archive.stream() - .filter(e -> e.getName().endsWith("/bin/camel.exe")) + ZipEntry x64 = archive.stream() + .filter(e -> e.getName().endsWith("/bin/camel-x64.exe")) + .findFirst() + .orElse(null); + assertNotNull(x64, "bin/camel-x64.exe must be included in " + zip.getFileName()); + assertTrue(x64.getSize() > 0, "bin/camel-x64.exe must not be empty"); + + ZipEntry arm64 = archive.stream() + .filter(e -> e.getName().endsWith("/bin/camel-arm64.exe")) .findFirst() .orElse(null); - assertNotNull(entry, "bin/camel.exe must be included in " + zip.getFileName()); - assertTrue(entry.getSize() > 0, "bin/camel.exe must not be empty"); + assertNotNull(arm64, "bin/camel-arm64.exe must be included in " + zip.getFileName()); + assertTrue(arm64.getSize() > 0, "bin/camel-arm64.exe must not be empty"); } } diff --git a/pom.xml b/pom.xml index b24505fee3b36..4d0ac5131ca7d 100644 --- a/pom.xml +++ b/pom.xml @@ -749,6 +749,7 @@ true + true diff --git a/tooling/camel-exe/README.md b/tooling/camel-exe/README.md index 390463492995e..7eb3f15e55f48 100644 --- a/tooling/camel-exe/README.md +++ b/tooling/camel-exe/README.md @@ -1,19 +1,20 @@ # Camel :: Exe -Standalone Maven module that builds the native Windows **`camel.exe`** bootstrap for the +Standalone Maven module that builds the native Windows **`camel-x64.exe`** and +**`camel-arm64.exe`** bootstraps for the [Camel CLI launcher](../../dsl/camel-jbang/camel-launcher). ## Purpose WinGet's portable installer (and similar package managers) require a genuine executable as the `camel` command. A `.bat` or `.cmd` shim is not enough — it produces a broken -`camel.exe` symlink. This module compiles a minimal x64 native binary that satisfies that +`camel.exe` symlink. This module compiles minimal native binaries that satisfy that contract. -`camel.exe` does not embed Java or run Camel itself. It locates `camel.bat` in the same -directory, forwards the caller's command line (preserving spaces and Unicode), and returns -its exit code. All Java discovery and CLI parsing remain in `camel.bat` and the launcher -JAR. +`camel-x64.exe` / `camel-arm64.exe` do not embed Java or run Camel. They locate +`camel.bat` in the same directory, forward the caller's command line (preserving spaces +and Unicode), and return its exit code. All Java discovery and CLI parsing remain in +`camel.bat` and the launcher JAR. ## Why a separate module? @@ -21,25 +22,27 @@ The launcher (`dsl/camel-jbang/camel-launcher`) is a fat JAR with a large upstre dependency tree (jbang core, plugins, repackager, and their transitive Camel deps). The native bootstrap is ~100 lines of C with **no Java dependencies**. Keeping it here allows: -- **Fast Windows CI** — `mvn -pl tooling/camel-exe verify` compiles and tests the exe - without building the full jbang graph. +- **Fast CI** — `mvn -pl tooling/camel-exe verify -Dcamel.exe.build=true` compiles and + tests the exe without building the full jbang graph. - **Clear separation** — packaging bootstrap vs. runtime launcher. -- **Reusable artifact** — `camel-launcher` copies the attached `exe` artifact into its - distribution on Windows (`bin/camel.exe` inside `camel-launcher-*-bin.zip`). +- **Reusable artifact** — `camel-launcher` copies the attached `exe` artifacts into its + distribution (`bin/camel-x64.exe` and `bin/camel-arm64.exe` inside + `camel-launcher-*-bin.zip`). ## Build and test -On a Windows x64 host with Microsoft C/C++ Build Tools (`cl.exe`) on PATH: +Requires [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) (release `20260616` pinned) +on `PATH`. Works on Linux, macOS, or Windows — no host-OS restriction. ```bash -mvn -pl tooling/camel-exe verify -Dcamel.exe.requireWindowsExe=true +mvn -pl tooling/camel-exe verify -Dcamel.exe.build=true ``` Release and integration builds that produce the launcher ZIP also build this module first: ```bash -mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify -Dcamel.launcher.requireWindowsExe=true +mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify -Dcamel.exe.build=true ``` -See [src/main/native/README.md](src/main/native/README.md) for MSVC setup, compiler +See [src/main/native/README.md](src/main/native/README.md) for toolchain setup, compiler flags, and the release-gate profile. diff --git a/tooling/camel-exe/pom.xml b/tooling/camel-exe/pom.xml index f05e18ea80f56..847e061bbcbb5 100644 --- a/tooling/camel-exe/pom.xml +++ b/tooling/camel-exe/pom.xml @@ -102,16 +102,24 @@ - build-windows-exe + build-native-exe - - windows - + + camel.exe.build + true + @@ -120,25 +128,44 @@ exec-maven-plugin - compile-camel-exe + compile-camel-exe-x64 + generate-test-resources + + exec + + + x86_64-w64-mingw32-clang + + -Wall + -Wextra + -O1 + -static + -mconsole + -municode + -o + ${project.build.directory}/camel-x64.exe + ${project.basedir}/src/main/native/camel.c + + + + + compile-camel-exe-arm64 generate-test-resources exec - cl.exe + aarch64-w64-mingw32-clang - /nologo - /W4 - /O1 - /MT - /Brepro - /Fe:${project.build.directory}\camel.exe - /Fo:${project.build.directory}\camel.obj - ${project.basedir}\src\main\native\camel.c - /link - /Brepro - /SUBSYSTEM:CONSOLE + -Wall + -Wextra + -O1 + -static + -mconsole + -municode + -o + ${project.build.directory}/camel-arm64.exe + ${project.basedir}/src/main/native/camel.c @@ -149,7 +176,23 @@ build-helper-maven-plugin - attach-camel-exe + attach-camel-exe-x64 + package + + attach-artifact + + + + + ${project.build.directory}/camel-x64.exe + exe + x64 + + + + + + attach-camel-exe-arm64 package attach-artifact @@ -157,8 +200,9 @@ - ${project.build.directory}/camel.exe + ${project.build.directory}/camel-arm64.exe exe + arm64 @@ -170,15 +214,15 @@ - require-windows-exe + require-native-exe - camel.exe.requireWindowsExe + camel.exe.build true @@ -198,7 +242,8 @@ - ${project.build.directory}/camel.exe + ${project.build.directory}/camel-x64.exe + ${project.build.directory}/camel-arm64.exe diff --git a/tooling/camel-exe/src/main/native/README.md b/tooling/camel-exe/src/main/native/README.md index 506b2bde8c711..2f990bf0531e5 100644 --- a/tooling/camel-exe/src/main/native/README.md +++ b/tooling/camel-exe/src/main/native/README.md @@ -1,26 +1,49 @@ # Native build (`camel.c`) -Compiler flags, MSVC setup, and release-gate details for the `camel.exe` bootstrap. -See the [module README](../../../README.md) for purpose, architecture, and integration -with `camel-launcher`. +Toolchain setup, compiler flags, and release-gate details for the `camel-x64.exe` and +`camel-arm64.exe` bootstraps. See the [module README](../../../README.md) for purpose, +architecture, and integration with `camel-launcher`. -## MSVC setup +## Toolchain: llvm-mingw -The `build-windows-exe` Maven profile activates automatically on Windows and invokes -`cl.exe`. MSVC must be on PATH — open a developer command prompt (e.g. run -`vcvars64.bat`) or provision MSVC in CI before running Maven. +The `build-native-exe` Maven profile cross-compiles for Windows x64 and arm64 using +[llvm-mingw](https://github.com/mstorsjo/llvm-mingw), a clang/LLVM-based mingw-w64 +toolchain. The build is host-OS-independent: it works on Linux, macOS, or Windows +wherever llvm-mingw is installed. -## Compiler invocation +### Installation - cl /nologo /W4 /O1 /MT /Brepro /Fe:target\camel.exe src\main\native\camel.c /link /Brepro /SUBSYSTEM:CONSOLE +Download the pinned release (`20260616`) from +[GitHub releases](https://github.com/mstorsjo/llvm-mingw/releases/tag/20260616): -- `/MT` — static CRT (no runtime DLL dependency) -- `/Brepro` — deterministic PE header timestamp on both compiler and linker, so - repeated builds from the same source produce byte-identical output +- **Linux (x86\_64):** `llvm-mingw-20260616-ucrt-ubuntu-22.04-x86_64.tar.xz` + (SHA256: `534b92e067b22a6b4441f48ae9240a3341b17825d04d577eab0cf85c44b4deda`) +- **macOS (universal):** `llvm-mingw-20260616-ucrt-macos-universal.tar.xz` + (SHA256: `2cab02a2e964bd4aae981150a45985d07c657cfa8d244959eb9e2dcc5eedd7b1`) + +Extract and add the `bin/` directory to `PATH` so that `x86_64-w64-mingw32-clang` and +`aarch64-w64-mingw32-clang` are available. + +## Compiler invocations + + x86_64-w64-mingw32-clang -Wall -Wextra -O1 -static -mconsole -municode -o target/camel-x64.exe src/main/native/camel.c + aarch64-w64-mingw32-clang -Wall -Wextra -O1 -static -mconsole -municode -o target/camel-arm64.exe src/main/native/camel.c + +- `-static` — statically links libgcc/compiler-rt; UCRT itself is dynamically imported + via `api-ms-win-crt-*.dll` API-set forwarders (OS components on Windows 10 1607+) +- `-mconsole` — sets PE subsystem to `CONSOLE`, equivalent to MSVC `/SUBSYSTEM:CONSOLE` +- `-municode`: selects the wide-character CRT startup so the `wmain` entry point links + and receives the Unicode command line + +## CRT linking difference from MSVC + +The previous MSVC build used `/MT` (static CRT, no runtime DLL dependency). The +clang/llvm-mingw build dynamically imports UCRT via `api-ms-win-crt-*.dll` API-set +forwarders. UCRT ships as an OS component on Windows 10 1607+, which is WinGet's +practical minimum target. ## Release gate -Release builds pass `-Dcamel.exe.requireWindowsExe=true`, which activates the -`require-windows-exe` profile. The enforcer fails the build if `target/camel.exe` is -absent. Because MSVC cannot cross-compile from macOS/Linux, the launcher ZIP/TAR that -carries `camel.exe` must be assembled on a Windows x64 host. +Release builds use `-Prelease`, which sets `camel.exe.build=true`, activating the +`require-native-exe` profile. The enforcer fails the build if `target/camel-x64.exe` or +`target/camel-arm64.exe` is absent. diff --git a/tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe/CamelExeBootstrapTest.java b/tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe/CamelExeBootstrapTest.java index 3171012393c3b..1b781c0f6e803 100644 --- a/tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe/CamelExeBootstrapTest.java +++ b/tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe/CamelExeBootstrapTest.java @@ -35,13 +35,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /** - * Windows-only behavioral test for the native camel.exe bootstrap. Requires the build-windows-exe profile to have - * produced target/camel.exe; the test fails loudly if it is missing so a broken native build does not pass silently. + * Windows-only behavioral test for the native camel.exe bootstrap. Requires the build-native-exe profile to have + * produced the platform-appropriate exe (target/camel-x64.exe on x86_64, target/camel-arm64.exe on aarch64); the test + * fails loudly if it is missing so a broken native build does not pass silently. */ @EnabledOnOs(OS.WINDOWS) class CamelExeBootstrapTest { - private static final Path BUILT_EXE = Paths.get("target/camel.exe"); + private static final String EXE_ARCH = "aarch64".equals(System.getProperty("os.arch")) ? "arm64" : "x64"; + private static final Path BUILT_EXE = Paths.get("target/camel-" + EXE_ARCH + ".exe"); private static final String CAPTURED_ARGS_FILE = "camel-args.txt"; private static final String CAPTURE_ARGS_SCRIPT = "capture-arg.ps1"; @@ -98,8 +100,8 @@ private List readCapturedArgs(Path dir) throws Exception { private Path stagedExe(Path dir) throws Exception { assertTrue(Files.exists(BUILT_EXE), - "target/camel.exe must be built by the build-windows-exe profile before this test"); - Path exe = dir.resolve("camel.exe"); + BUILT_EXE + " must be built by the build-native-exe profile before this test"); + Path exe = dir.resolve(BUILT_EXE.getFileName().toString()); Files.copy(BUILT_EXE, exe); return exe; } diff --git a/tooling/maven/camel-repackager-maven-plugin/src/main/java/org/apache/camel/maven/RepackageMojo.java b/tooling/maven/camel-repackager-maven-plugin/src/main/java/org/apache/camel/maven/RepackageMojo.java index 3c2bab83feacf..7c1c679b2ea74 100644 --- a/tooling/maven/camel-repackager-maven-plugin/src/main/java/org/apache/camel/maven/RepackageMojo.java +++ b/tooling/maven/camel-repackager-maven-plugin/src/main/java/org/apache/camel/maven/RepackageMojo.java @@ -132,7 +132,8 @@ private void getLibraries(LibraryCallback callback) throws IOException { boolean includeArtifact(Artifact artifact) { // Only jar-type artifacts are valid Spring Boot loader libraries. Non-jar artifacts // (e.g. the native camel-exe:exe bootstrap, which camel-launcher depends on purely to - // stage bin/camel.exe via the assembly descriptor) must never be embedded in BOOT-INF/lib. + // stage bin/camel-x64.exe and bin/camel-arm64.exe via the assembly descriptor) must never + // be embedded in BOOT-INF/lib. if (!"jar".equals(artifact.getType())) { return false; } diff --git a/tooling/maven/camel-repackager-maven-plugin/src/test/java/org/apache/camel/maven/RepackageMojoTest.java b/tooling/maven/camel-repackager-maven-plugin/src/test/java/org/apache/camel/maven/RepackageMojoTest.java index 329a43d571978..8f2121d56dad6 100644 --- a/tooling/maven/camel-repackager-maven-plugin/src/test/java/org/apache/camel/maven/RepackageMojoTest.java +++ b/tooling/maven/camel-repackager-maven-plugin/src/test/java/org/apache/camel/maven/RepackageMojoTest.java @@ -77,7 +77,8 @@ public void testCompileScopedJarIsIncluded() { @Test public void testNonJarArtifactIsExcludedEvenWhenCompileScoped() { // camel-launcher depends on camel-exe:exe purely so the assembly descriptor can stage - // bin/camel.exe; it must never end up embedded as a Spring Boot loader library. + // bin/camel-x64.exe and bin/camel-arm64.exe; it must never end up embedded as a Spring + // Boot loader library. RepackageMojo mojo = new RepackageMojo(); Artifact artifact = artifact("org.apache.camel", "camel-exe", "exe", Artifact.SCOPE_COMPILE); From 319b208f82048e1729b28f018f796fba0845847b Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:16:47 -0400 Subject: [PATCH 2/5] CAMEL-23703: camel-launcher - self-provision llvm-mingw for snapshot deploys Extract the llvm-mingw download/verify logic into a shared install script (.github/actions/setup-llvm-mingw/install-llvm-mingw.sh) so the pinned version and SHA256 live in one place, used by both the GitHub Actions composite action and Jenkinsfile.deploy. Jenkinsfile.deploy now reuses an agent-provided toolchain when the cross-compilers are already on PATH, and otherwise downloads the pinned, SHA256-verified copy into a persistent cache. This removes the requirement to pre-provision llvm-mingw on the ASF Jenkins agents raised in PR review. Co-Authored-By: Claude Opus 4.8 --- .github/actions/setup-llvm-mingw/action.yml | 12 +---- .../setup-llvm-mingw/install-llvm-mingw.sh | 49 +++++++++++++++++++ Jenkinsfile.deploy | 28 +++++++++-- .../modules/ROOT/pages/release-guide.adoc | 13 +++-- 4 files changed, 82 insertions(+), 20 deletions(-) create mode 100755 .github/actions/setup-llvm-mingw/install-llvm-mingw.sh diff --git a/.github/actions/setup-llvm-mingw/action.yml b/.github/actions/setup-llvm-mingw/action.yml index 397d697656f63..1b1b99ec0aff9 100644 --- a/.github/actions/setup-llvm-mingw/action.yml +++ b/.github/actions/setup-llvm-mingw/action.yml @@ -25,14 +25,6 @@ runs: using: "composite" steps: - run: | - VERSION='20260616' - SHA256='534b92e067b22a6b4441f48ae9240a3341b17825d04d577eab0cf85c44b4deda' - TARBALL="llvm-mingw-${VERSION}-ucrt-ubuntu-22.04-x86_64.tar.xz" - INSTALL_DIR="${RUNNER_TEMP}/llvm-mingw" - mkdir -p "$INSTALL_DIR" - curl -fsSL -o "$TARBALL" \ - "https://github.com/mstorsjo/llvm-mingw/releases/download/${VERSION}/${TARBALL}" - echo "${SHA256} ${TARBALL}" | sha256sum -c - - tar -xf "$TARBALL" -C "$INSTALL_DIR" - echo "${INSTALL_DIR}/llvm-mingw-${VERSION}-ucrt-ubuntu-22.04-x86_64/bin" >> "$GITHUB_PATH" + BIN_DIR="$("${GITHUB_ACTION_PATH}/install-llvm-mingw.sh" "${RUNNER_TEMP}/llvm-mingw")" + echo "${BIN_DIR}" >> "$GITHUB_PATH" shell: bash diff --git a/.github/actions/setup-llvm-mingw/install-llvm-mingw.sh b/.github/actions/setup-llvm-mingw/install-llvm-mingw.sh new file mode 100755 index 0000000000000..8b0c8c465338a --- /dev/null +++ b/.github/actions/setup-llvm-mingw/install-llvm-mingw.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Installs a pinned, SHA256-verified release of llvm-mingw and prints the +# absolute path to its bin/ directory on stdout. Single source of truth for the +# pinned version and hash, shared by the GitHub Actions composite action +# (.github/actions/setup-llvm-mingw/action.yml) and Jenkinsfile.deploy. +# +# Usage: install-llvm-mingw.sh +# +# If the toolchain is already present under the download is +# skipped, so a persistent cache directory makes repeated runs cheap. +# + +set -eo pipefail + +VERSION='20260616' +SHA256='534b92e067b22a6b4441f48ae9240a3341b17825d04d577eab0cf85c44b4deda' +TARBALL="llvm-mingw-${VERSION}-ucrt-ubuntu-22.04-x86_64.tar.xz" + +INSTALL_DIR="${1:?usage: install-llvm-mingw.sh }" +DEST="${INSTALL_DIR}/llvm-mingw-${VERSION}-ucrt-ubuntu-22.04-x86_64" + +if [ ! -x "${DEST}/bin/x86_64-w64-mingw32-clang" ]; then + mkdir -p "$INSTALL_DIR" + curl -fsSL -o "${INSTALL_DIR}/${TARBALL}" \ + "https://github.com/mstorsjo/llvm-mingw/releases/download/${VERSION}/${TARBALL}" + echo "${SHA256} ${INSTALL_DIR}/${TARBALL}" | sha256sum -c - + tar -xf "${INSTALL_DIR}/${TARBALL}" -C "$INSTALL_DIR" + rm -f "${INSTALL_DIR}/${TARBALL}" +fi + +printf '%s\n' "${DEST}/bin" diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy index 4e81511a99583..0bbcd7a9e4bbc 100644 --- a/Jenkinsfile.deploy +++ b/Jenkinsfile.deploy @@ -55,7 +55,7 @@ pipeline { } } - stage('Verify llvm-mingw') { + stage('Setup llvm-mingw') { when { anyOf { branch 'main' @@ -64,11 +64,29 @@ pipeline { } } steps { + script { + // Prefer an agent-provided llvm-mingw when the cross-compilers are already + // on PATH; otherwise download the pinned, SHA256-verified toolchain into a + // persistent cache so snapshot deploys don't require it to be pre-provisioned. + // The install script is the single source of truth shared with + // .github/actions/setup-llvm-mingw/action.yml. + env.LLVM_MINGW_BIN = sh( + returnStdout: true, + script: ''' + set -eo pipefail + if command -v x86_64-w64-mingw32-clang >/dev/null 2>&1 \ + && command -v aarch64-w64-mingw32-clang >/dev/null 2>&1; then + dirname "$(command -v x86_64-w64-mingw32-clang)" + else + ./.github/actions/setup-llvm-mingw/install-llvm-mingw.sh "$HOME/.cache/llvm-mingw" + fi + ''' + ).trim() + } sh ''' for compiler in x86_64-w64-mingw32-clang aarch64-w64-mingw32-clang; do - if ! command -v "$compiler" >/dev/null 2>&1; then - echo "ERROR: $compiler not found on PATH." - echo "Install llvm-mingw per docs/user-manual/modules/ROOT/pages/release-guide.adoc" + if ! [ -x "${LLVM_MINGW_BIN}/${compiler}" ]; then + echo "ERROR: $compiler not found under ${LLVM_MINGW_BIN}." exit 1 fi done @@ -85,7 +103,7 @@ pipeline { } } steps { - sh "./mvnw $MAVEN_PARAMS -Pdeploy,apache-snapshots -Dquickly clean deploy" + sh "PATH=\"${LLVM_MINGW_BIN}:\$PATH\" ./mvnw $MAVEN_PARAMS -Pdeploy,apache-snapshots -Dquickly clean deploy" } } diff --git a/docs/user-manual/modules/ROOT/pages/release-guide.adoc b/docs/user-manual/modules/ROOT/pages/release-guide.adoc index 36c1d59c333f4..a4ae6bac35bfc 100644 --- a/docs/user-manual/modules/ROOT/pages/release-guide.adoc +++ b/docs/user-manual/modules/ROOT/pages/release-guide.adoc @@ -14,11 +14,14 @@ ApacheCon, for instance). * Make sure you have the correct maven configuration in `~/.m2/settings.xml`. * You may want to get familiar with the release settings in the parent Apache POM. * Make sure you are using the expected supported Java version. -* Install https://github.com/mstorsjo/llvm-mingw/releases/tag/20260616[llvm-mingw 20260616] and -add its `bin/` directory to `PATH`. This provides the `x86_64-w64-mingw32-clang` and -`aarch64-w64-mingw32-clang` cross-compilers used to build the native `camel-x64.exe` and -`camel-arm64.exe` Windows bootstraps. The `-Prelease` Maven profile activates the cross-compile -automatically; a missing toolchain causes a loud build failure (never a silent skip). +* The `-Prelease` Maven profile cross-compiles the native `camel-x64.exe` and `camel-arm64.exe` +Windows bootstraps using the `x86_64-w64-mingw32-clang` and `aarch64-w64-mingw32-clang` compilers +from https://github.com/mstorsjo/llvm-mingw/releases/tag/20260616[llvm-mingw 20260616]. For a +local release, install that toolchain and add its `bin/` directory to `PATH`; a missing toolchain +causes a loud build failure (never a silent skip). The CI snapshot-deploy pipeline +(`Jenkinsfile.deploy`) reuses an agent-provided toolchain when it is already on `PATH` and +otherwise downloads the pinned, SHA256-verified copy automatically, so no manual provisioning of +the Jenkins agents is required. [[ReleaseGuide-GPG]] == GPG setup From 665b20fc0d304b3b3cd10f8c91e6c255b2be4535 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:32:55 -0400 Subject: [PATCH 3/5] CAMEL-23703: camel-launcher CI - use plain mvn for the small camel-exe reactor The camel-exe job builds only a handful of modules, so the mvnd daemon adds no parallelism benefit and exposed a flaky daemon cold-start timeout. Run plain mvn there; keep mvnd for camel-launcher-native, whose -am reactor pulls in the broader component tree. Document the guidance in install-mvnd. Co-Authored-By: Claude Opus 4.8 --- .github/actions/install-mvnd/action.yml | 4 ++++ .github/workflows/camel-launcher-native-exe.yml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/actions/install-mvnd/action.yml b/.github/actions/install-mvnd/action.yml index d5770110c99d5..bb6a94d39def9 100644 --- a/.github/actions/install-mvnd/action.yml +++ b/.github/actions/install-mvnd/action.yml @@ -15,6 +15,10 @@ # limitations under the License. # +# Use mvnd only for jobs that build a large, parallelizable reactor, where the +# daemon's concurrent module builds outweigh its cold-start cost. For small +# reactors (a handful of modules) prefer plain `mvn`: mvnd adds no parallelism +# benefit there and introduces a daemon startup timeout as a flaky failure mode. name: 'install-mvnd' description: 'Install the maven daemon' inputs: diff --git a/.github/workflows/camel-launcher-native-exe.yml b/.github/workflows/camel-launcher-native-exe.yml index 2c3e1be5b9cfc..d3ec90479920f 100644 --- a/.github/workflows/camel-launcher-native-exe.yml +++ b/.github/workflows/camel-launcher-native-exe.yml @@ -102,12 +102,12 @@ jobs: distribution: 'temurin' java-version: '21' cache: 'maven' - - id: install-mvnd - uses: ./.github/actions/install-mvnd - uses: ./.github/actions/setup-llvm-mingw - name: Build and test native camel.exe (x64 + arm64) + # 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: | - ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd $MVND_OPTS -pl buildingtools,tooling/camel-exe -am verify -Dcamel.exe.build=true -DskipTests + mvn -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: From 698c500ac6fe4c827d8081d9268624c0d3f0f08c Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:23:50 -0400 Subject: [PATCH 4/5] CAMEL-23703: camel-launcher CI - scope launcher-native to snapshots, drop mvnd Remove -am from the camel-launcher-native job so it builds only the listed modules and resolves the launcher's upstream modules as the current version's snapshot from Apache snapshots, instead of rebuilding the whole launcher reactor from source. Add a preflight that fails early with a clear message if that snapshot tree is not published yet, using camel-launcher (the terminal module, deployed after its dependencies) as the sentinel. Switch the job to plain mvn: with -am gone the reactor is only a few modules, so the mvnd daemon buys no parallelism and its cold-start was a flaky failure mode. Pin all jobs to Java 25 to stay aligned with the highest producer of the snapshots and avoid class-version mismatches on the compile classpath. Co-Authored-By: Claude Opus 4.8 --- .../workflows/camel-launcher-native-exe.yml | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/.github/workflows/camel-launcher-native-exe.yml b/.github/workflows/camel-launcher-native-exe.yml index d3ec90479920f..952aadf8c7009 100644 --- a/.github/workflows/camel-launcher-native-exe.yml +++ b/.github/workflows/camel-launcher-native-exe.yml @@ -96,11 +96,11 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'temurin' - java-version: '21' + java-version: '25' cache: 'maven' - uses: ./.github/actions/setup-llvm-mingw - name: Build and test native camel.exe (x64 + arm64) @@ -125,11 +125,11 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'temurin' - java-version: '21' + java-version: '25' cache: 'maven' - name: Download Windows x64 camel.exe uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -149,19 +149,45 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'temurin' - java-version: '21' + java-version: '25' cache: 'maven' - - id: install-mvnd - uses: ./.github/actions/install-mvnd + - name: Verify launcher snapshot dependencies are published + # Without -am the launcher's upstream modules are not built here; they must + # already be deployed as the current version's snapshot on the Apache + # snapshot repository. camel-launcher is the terminal module, deployed after + # its dependencies, so its presence stands in as a sentinel for the whole + # upstream tree. Fail early with a clear message instead of a deep Maven + # resolution error mid-build. + run: | + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout -N) + META="https://repository.apache.org/snapshots/org/apache/camel/camel-launcher/${VERSION}/maven-metadata.xml" + echo "Checking ${META}" + if ! curl -fsSL -o /dev/null "${META}"; then + echo "ERROR: camel-launcher ${VERSION} is not on the Apache snapshot repository." + echo "This job resolves the launcher's upstream modules from there (no -am build)." + echo "Wait for a main-branch snapshot deploy of ${VERSION}, or rebuild with -am." + exit 1 + fi + echo "Launcher upstream snapshots for ${VERSION} are available." - uses: ./.github/actions/setup-llvm-mingw - 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 + # as the current version's snapshot from Apache snapshots rather than being + # rebuilt from source. This PR only touches camel-exe/camel-launcher, so those + # upstream snapshots are unchanged, and the job drops from the full launcher + # reactor to a handful of modules. + # + # Plain mvn: once -am is gone this reactor is only a few modules, so the mvnd + # daemon brings no parallelism benefit and adds a cold-start failure mode. + # -P apache-snapshots is required here so the upstream modules resolve. run: | - ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd $MVND_OPTS \ - -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify \ + mvn -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: | From d5b1abc7c2b48ab1e1d72721dabc04118a871a40 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Fri, 17 Jul 2026 01:27:39 -0400 Subject: [PATCH 5/5] CAMEL-23703: camel-launcher CI - gate native camel.exe build to main and camel-4.22.x The snapshot-deploy pipeline runs on main, camel-4.18.x and camel-4.14.x. The native camel.exe cross-compile only exists on main (and the upcoming camel-4.22.x LTS), so the llvm-mingw setup and -Dcamel.exe.build no longer run on the older LTS branches; those branches keep deploying their snapshots without the native build. Co-Authored-By: Claude Opus 4.8 --- Jenkinsfile.deploy | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy index 0bbcd7a9e4bbc..cea2360726318 100644 --- a/Jenkinsfile.deploy +++ b/Jenkinsfile.deploy @@ -17,7 +17,7 @@ def AGENT_LABEL = env.AGENT_LABEL ?: 'ubuntu' def JDK_NAME = env.JDK_NAME ?: 'jdk_25_latest' -def MAVEN_PARAMS = "-U -B -e -fae -V -Dnoassembly -Dcamel.exe.build=true " +def MAVEN_PARAMS = "-U -B -e -fae -V -Dnoassembly " pipeline { @@ -59,8 +59,7 @@ pipeline { when { anyOf { branch 'main' - branch 'camel-4.18.x' - branch 'camel-4.14.x' + branch 'camel-4.22.x' } } steps { @@ -103,7 +102,15 @@ pipeline { } } steps { - sh "PATH=\"${LLVM_MINGW_BIN}:\$PATH\" ./mvnw $MAVEN_PARAMS -Pdeploy,apache-snapshots -Dquickly clean deploy" + script { + // The native camel.exe cross-compile only exists on main and the upcoming + // camel-4.22.x LTS. Older LTS branches still deploy their snapshots, but + // without the native build (no -Dcamel.exe.build and no llvm-mingw on PATH). + def nativeExe = env.BRANCH_NAME in ['main', 'camel-4.22.x'] + def exeParams = nativeExe ? '-Dcamel.exe.build=true ' : '' + def pathPrefix = nativeExe ? "PATH=\"${env.LLVM_MINGW_BIN}:\$PATH\" " : '' + sh "${pathPrefix}./mvnw $MAVEN_PARAMS ${exeParams}-Pdeploy,apache-snapshots -Dquickly clean deploy" + } } }