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/actions/setup-llvm-mingw/action.yml b/.github/actions/setup-llvm-mingw/action.yml new file mode 100644 index 0000000000000..1b1b99ec0aff9 --- /dev/null +++ b/.github/actions/setup-llvm-mingw/action.yml @@ -0,0 +1,30 @@ +# +# 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: | + 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/.github/workflows/camel-launcher-native-exe.yml b/.github/workflows/camel-launcher-native-exe.yml new file mode 100644 index 0000000000000..952aadf8c7009 --- /dev/null +++ b/.github/workflows/camel-launcher-native-exe.yml @@ -0,0 +1,202 @@ +# +# 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. +# + +# 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: + branches: + - main + paths: + - 'tooling/camel-exe/**' + - 'dsl/camel-jbang/camel-launcher/**' + - '.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/actions/setup-llvm-mingw/**' + - '.github/workflows/camel-launcher-native-exe.yml' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + camel-exe: ${{ steps.changes.outputs.camel-exe }} + camel-launcher: ${{ steps.changes.outputs.camel-launcher }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + fetch-depth: 0 + - id: changes + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "camel-exe=true" >> "$GITHUB_OUTPUT" + echo "camel-launcher=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${{ github.event_name }}" = "pull_request" ]; then + CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD)" + else + CHANGED="$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}")" + fi + + echo "Changed files:" + echo "$CHANGED" + + camel_exe=false + camel_launcher=false + + 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/actions/setup-llvm-mingw/|\.github/workflows/camel-launcher-native-exe\.yml)'; then + camel_launcher=true + fi + + echo "camel-exe=$camel_exe" >> "$GITHUB_OUTPUT" + echo "camel-launcher=$camel_launcher" >> "$GITHUB_OUTPUT" + + camel-exe: + needs: detect-changes + if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-exe == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Set up JDK 25 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + - 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: | + 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: + 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: + persist-credentials: false + - name: Set up JDK 25 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + - 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: | + mvn -B -ntp -pl buildingtools,tooling/camel-exe -am test -Dtest=CamelExeBootstrapTest -Dsurefire.failIfNoSpecifiedTests=false + + camel-launcher-native: + needs: detect-changes + if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-launcher == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Set up JDK 25 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + - 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: | + 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: | + 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/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml deleted file mode 100644 index f4032f6ce1086..0000000000000 --- a/.github/workflows/camel-launcher-windows.yml +++ /dev/null @@ -1,139 +0,0 @@ -# -# 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: Camel launcher Windows build - -on: - push: - branches: - - main - paths: - - 'tooling/camel-exe/**' - - 'dsl/camel-jbang/camel-launcher/**' - - '.github/workflows/camel-launcher-windows.yml' - pull_request: - branches: - - main - paths: - - 'tooling/camel-exe/**' - - 'dsl/camel-jbang/camel-launcher/**' - - '.github/workflows/camel-launcher-windows.yml' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - detect-changes: - runs-on: ubuntu-latest - outputs: - camel-exe: ${{ steps.changes.outputs.camel-exe }} - camel-launcher: ${{ steps.changes.outputs.camel-launcher }} - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - id: changes - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "camel-exe=true" >> "$GITHUB_OUTPUT" - echo "camel-launcher=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [ "${{ github.event_name }}" = "pull_request" ]; then - CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD)" - else - CHANGED="$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}")" - fi - - echo "Changed files:" - echo "$CHANGED" - - camel_exe=false - camel_launcher=false - - if echo "$CHANGED" | grep -qE '^(tooling/camel-exe/|\.github/workflows/camel-launcher-windows\.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 - camel_launcher=true - fi - - echo "camel-exe=$camel_exe" >> "$GITHUB_OUTPUT" - echo "camel-launcher=$camel_launcher" >> "$GITHUB_OUTPUT" - - camel-exe: - needs: detect-changes - if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-exe == 'true' - runs-on: windows-2022 - 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' - - name: Build and test native camel.exe - shell: cmd - 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 - - camel-launcher-windows: - needs: detect-changes - if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-launcher == 'true' - runs-on: windows-2022 - 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' - - name: Build launcher with native camel.exe - shell: cmd - 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 - 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() - } diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy index 4d588e76bd236..cea2360726318 100644 --- a/Jenkinsfile.deploy +++ b/Jenkinsfile.deploy @@ -55,6 +55,44 @@ pipeline { } } + stage('Setup llvm-mingw') { + when { + anyOf { + branch 'main' + branch 'camel-4.22.x' + } + } + 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 ! [ -x "${LLVM_MINGW_BIN}/${compiler}" ]; then + echo "ERROR: $compiler not found under ${LLVM_MINGW_BIN}." + exit 1 + fi + done + ''' + } + } + stage('Build & Deploy') { when { anyOf { @@ -64,7 +102,15 @@ pipeline { } } steps { - sh "./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" + } } } @@ -89,4 +135,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..a4ae6bac35bfc 100644 --- a/docs/user-manual/modules/ROOT/pages/release-guide.adoc +++ b/docs/user-manual/modules/ROOT/pages/release-guide.adoc @@ -14,6 +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. +* 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 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);