From 3dd73b049a5ebf5176351f8914b264413ef8d114 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:54:40 -0400 Subject: [PATCH 01/30] CAMEL-23703: camel-launcher - native x64 camel.exe bootstrap source --- .../camel-launcher/src/main/native/camel.c | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 dsl/camel-jbang/camel-launcher/src/main/native/camel.c diff --git a/dsl/camel-jbang/camel-launcher/src/main/native/camel.c b/dsl/camel-jbang/camel-launcher/src/main/native/camel.c new file mode 100644 index 0000000000000..0f2ee4c27ef31 --- /dev/null +++ b/dsl/camel-jbang/camel-launcher/src/main/native/camel.c @@ -0,0 +1,107 @@ +/* + * 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. + */ + +/* + * camel.exe - minimal WinGet-compatible bootstrap for the Camel CLI launcher. + * + * It locates the camel.bat sitting in the same directory, forwards the caller's + * exact command-line tail (preserving Unicode and Windows quoting), inherits the + * standard streams, and returns the child process exit code. It performs NO Java + * discovery and NO Camel option parsing; that all lives in camel.bat. + */ + +#include +#include +#include +#include + +/* Return the command-line tail after argv[0], preserving the caller's quoting. */ +static LPWSTR skip_argv0(LPWSTR cmd) { + LPWSTR p = cmd; + if (*p == L'"') { + p++; + while (*p && *p != L'"') { + p++; + } + if (*p == L'"') { + p++; + } + } else { + while (*p && *p != L' ' && *p != L'\t') { + p++; + } + } + while (*p == L' ' || *p == L'\t') { + p++; + } + return p; +} + +int wmain(void) { + wchar_t exePath[MAX_PATH]; + DWORD n = GetModuleFileNameW(NULL, exePath, MAX_PATH); + if (n == 0 || n >= MAX_PATH) { + fwprintf(stderr, L"camel: cannot resolve launcher path\n"); + return 1; + } + + /* Strip the exe filename, leaving the directory. */ + wchar_t *slash = wcsrchr(exePath, L'\\'); + if (slash == NULL) { + fwprintf(stderr, L"camel: unexpected launcher path\n"); + return 1; + } + *slash = L'\0'; + + LPWSTR tail = skip_argv0(GetCommandLineW()); + + /* + * cmd.exe /S /C ""\camel.bat" " + * With /S and a command wrapped in an outer pair of quotes, cmd strips the + * first and last quote and executes the remainder verbatim, so the inner + * quotes around the (possibly spaced/Unicode) camel.bat path survive. + */ + size_t cap = wcslen(exePath) + wcslen(tail) + 64; + LPWSTR cmdline = (LPWSTR) malloc(cap * sizeof(wchar_t)); + if (cmdline == NULL) { + fwprintf(stderr, L"camel: out of memory\n"); + return 1; + } + _snwprintf_s(cmdline, cap, _TRUNCATE, + L"cmd.exe /S /C \"\"%s\\camel.bat\" %s\"", exePath, tail); + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + /* bInheritHandles=TRUE and no STARTF_USESTDHANDLES: child shares our console. */ + if (!CreateProcessW(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { + fwprintf(stderr, L"camel: failed to start camel.bat (error %lu)\n", GetLastError()); + free(cmdline); + return 1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + DWORD code = 1; + GetExitCodeProcess(pi.hProcess, &code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + free(cmdline); + return (int) code; +} From 290e488842b099131c22635589606c0494e4a5f6 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:55:22 -0400 Subject: [PATCH 02/30] CAMEL-23703: camel-launcher - document native camel.exe build --- .../camel-launcher/src/main/native/README.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 dsl/camel-jbang/camel-launcher/src/main/native/README.md diff --git a/dsl/camel-jbang/camel-launcher/src/main/native/README.md b/dsl/camel-jbang/camel-launcher/src/main/native/README.md new file mode 100644 index 0000000000000..a8f2b088f52af --- /dev/null +++ b/dsl/camel-jbang/camel-launcher/src/main/native/README.md @@ -0,0 +1,39 @@ +# camel.exe native bootstrap + +`camel.c` compiles to a minimal x64 `camel.exe` that WinGet's portable installer +exposes as the `camel` command. WinGet's portable command must be a real +executable (a `.bat`/`.cmd`/`.ps1` target produces a broken `camel.exe` symlink), +so a genuine native binary is required. + +## What it does + +`camel.exe` locates the `camel.bat` in the same directory, forwards the caller's +exact command-line tail (preserving Unicode and Windows quoting) to +`cmd.exe /S /C`, inherits the standard streams, and returns the child exit code. +It performs no Java discovery and no Camel option parsing; all of that lives in +`camel.bat`. + +## Build + +Built by the `build-windows-exe` Maven profile, which activates automatically on +Windows. It requires the Microsoft C/C++ Build Tools (`cl.exe`) on PATH — set up +a developer command prompt (e.g. `vcvars64.bat`) or a CI action that provisions +MSVC before running Maven: + + mvn -pl dsl/camel-jbang/camel-launcher package + +The compiler is invoked as: + + cl /nologo /W4 /O1 /MT /Brepro /Fe:target\camel.exe src\main\native\camel.c /link /Brepro /SUBSYSTEM:CONSOLE + +`/MT` links the static CRT (no runtime DLL dependency); `/Brepro` on both the +compiler and linker makes the PE header timestamp deterministic, so repeated +builds from the same source produce byte-identical output. + +## Release gate + +Release builds pass `-Dcamel.launcher.requireWindowsExe=true`, which makes the +`require-windows-exe` profile fail the build if `target/camel.exe` is absent. This +prevents publishing a Windows package that cannot satisfy WinGet's portable +command contract. Because MSVC cannot cross-compile from macOS/Linux, the release +ZIP/TAR that carries `camel.exe` must be assembled on a Windows x64 host. From 27ac26ddb8c9a2d261f82f6d2617fb6fe1ce674c Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:59:12 -0400 Subject: [PATCH 03/30] CAMEL-23703: camel-launcher - Maven profiles to build and gate native camel.exe --- dsl/camel-jbang/camel-launcher/pom.xml | 91 ++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/dsl/camel-jbang/camel-launcher/pom.xml b/dsl/camel-jbang/camel-launcher/pom.xml index 3694faf67e92d..73f7dea0afd17 100644 --- a/dsl/camel-jbang/camel-launcher/pom.xml +++ b/dsl/camel-jbang/camel-launcher/pom.xml @@ -325,4 +325,95 @@ + + + + + build-windows-exe + + + windows + + + + + + org.codehaus.mojo + exec-maven-plugin + + + compile-camel-exe + prepare-package + + exec + + + cl.exe + + /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 + + + + + + + + + + + + require-windows-exe + + + camel.launcher.requireWindowsExe + true + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + require-camel-exe + prepare-package + + enforce + + + + + + ${project.build.directory}/camel.exe + + + + true + + + + + + + + From cf4bb4a4716e7525d1b904688c8c0bd88867d3cb Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:00:11 -0400 Subject: [PATCH 04/30] CAMEL-23703: camel-launcher - include native camel.exe in bin archives --- .../camel-launcher/src/main/assembly/bin.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 32432cac0ccb7..16f1d62ad1d9d 100644 --- a/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml +++ b/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml @@ -70,5 +70,20 @@ *.* + + ${project.basedir}/src/examples + examples + + *.* + + + + ${project.build.directory} + bin + + camel.exe + + 0755 + From 6d4aaf276a0b737f8500f7cd42d6797774cd3e2f Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:00:21 -0400 Subject: [PATCH 05/30] CAMEL-23703: camel-launcher - Windows behavioral test for native camel.exe (CI) --- .../jbang/launcher/CamelExeBootstrapTest.java | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java diff --git a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java new file mode 100644 index 0000000000000..5aebdcbc21316 --- /dev/null +++ b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java @@ -0,0 +1,144 @@ +/* + * 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. + */ +package org.apache.camel.dsl.jbang.launcher; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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. + */ +@EnabledOnOs(OS.WINDOWS) +class CamelExeBootstrapTest { + + private static final Path BUILT_EXE = Paths.get("target/camel.exe"); + + /** Writes a fake camel.bat that echoes each argument on its own line and returns exitCode. */ + private Path fakeBat(Path dir, int exitCode) throws Exception { + Path bat = dir.resolve("camel.bat"); + String body = "@echo off\r\n" + + ":loop\r\n" + + "if \"%~1\"==\"\" goto done\r\n" + + "echo ARG=%~1\r\n" + + "shift\r\n" + + "goto loop\r\n" + + ":done\r\n" + + "exit /b " + exitCode + "\r\n"; + Files.writeString(bat, body, StandardCharsets.UTF_8); + return bat; + } + + 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"); + Files.copy(BUILT_EXE, exe); + return exe; + } + + private static final class Result { + int exit; + String stdout; + } + + private Result run(Path exe, String... args) throws Exception { + List cmd = new ArrayList<>(); + cmd.add(exe.toString()); + for (String a : args) { + cmd.add(a); + } + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.redirectErrorStream(false); + Process p = pb.start(); + String out = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + assertTrue(p.waitFor(60, TimeUnit.SECONDS), "camel.exe did not exit in time"); + Result r = new Result(); + r.exit = p.exitValue(); + r.stdout = out; + return r; + } + + @Test + void forwardsArgumentsToAdjacentCamelBat(@TempDir Path dir) throws Exception { + fakeBat(dir, 0); + Path exe = stagedExe(dir); + + Result r = run(exe, "version"); + + assertEquals(0, r.exit); + assertTrue(r.stdout.contains("ARG=version"), r.stdout); + } + + @Test + void preservesArgumentWithSpaces(@TempDir Path dir) throws Exception { + fakeBat(dir, 0); + Path exe = stagedExe(dir); + + Result r = run(exe, "run", "my route.yaml"); + + assertTrue(r.stdout.contains("ARG=run"), r.stdout); + assertTrue(r.stdout.contains("ARG=my route.yaml"), "spaced argument must survive as one token: " + r.stdout); + } + + @Test + void preservesUnicodeArgument(@TempDir Path dir) throws Exception { + fakeBat(dir, 0); + Path exe = stagedExe(dir); + + Result r = run(exe, "run", "rüte-über.yaml"); + + assertTrue(r.stdout.contains("rüte-über.yaml"), "Unicode argument must survive: " + r.stdout); + } + + @Test + void propagatesChildExitCode(@TempDir Path dir) throws Exception { + fakeBat(dir, 42); + Path exe = stagedExe(dir); + + Result r = run(exe, "version"); + + assertEquals(42, r.exit, "camel.exe must return camel.bat's exit code"); + } + + @Test + void worksWhenExeDirectoryHasSpaces(@TempDir Path base) throws Exception { + Path dir = base.resolve("Program Files X"); + Files.createDirectories(dir); + fakeBat(dir, 0); + Path exe = stagedExe(dir); + + Result r = run(exe, "version"); + + assertEquals(0, r.exit, r.stdout); + assertTrue(r.stdout.contains("ARG=version"), r.stdout); + } +} From 0dd0340ec978cde9a39f6e1f68b6ed16574f5f8e Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:01:30 -0400 Subject: [PATCH 06/30] CAMEL-23703: ci - build and test native camel.exe on Windows x64 --- .github/workflows/camel-launcher-windows.yml | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/camel-launcher-windows.yml diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml new file mode 100644 index 0000000000000..918ee7e89de4d --- /dev/null +++ b/.github/workflows/camel-launcher-windows.yml @@ -0,0 +1,61 @@ +# +# 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 + pull_request: + branches: + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + camel-launcher-windows: + runs-on: windows-2022 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Set up JDK 17 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + - name: Build launcher (compiles native camel.exe) and run tests + 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 dsl/camel-jbang/camel-launcher -am install -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 + $names = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName).Entries.FullName + if ($names -notcontains 'bin/camel.exe') { throw 'camel.exe missing from launcher archive' } From 9606fb6120c6b780e5c2348c1d9035621f71f549 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:05:31 -0400 Subject: [PATCH 07/30] CAMEL-23703: docs - native camel.exe note in 4.22 upgrade guide --- .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 0454ac8a835a2..18f4722a27f25 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 @@ -80,6 +80,16 @@ The Gentoo `java-config` auto-detection and the IBM AIX `$JAVA_HOME/jre/sh/java` removed; set `JAVA_HOME` or `JAVACMD` explicitly on those platforms. `JAVA_OPTS` handling is unchanged: when unset, the default `-Xmx512m` heap is applied. +==== Native `camel.exe` in the launcher distribution + +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 +command (such as WinGet's portable installer) can expose `camel` directly. Direct +users may continue to invoke `bin\camel.bat`; both behave identically. Windows +ARM64 and x86 are not yet supported. + === camel-langchain4j-agent The `Agent.chat()` method return type has changed from `String` to `Result` (from `dev.langchain4j.service.Result`). From 177b0ee4c016f7d0d76bd526315d581f21400e43 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:07:13 -0400 Subject: [PATCH 08/30] CAMEL-23703: camel-launcher - apply formatter and impsort --- dsl/camel-jbang/camel-launcher/src/main/native/camel.c | 3 +-- .../camel/dsl/jbang/launcher/CamelExeBootstrapTest.java | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/dsl/camel-jbang/camel-launcher/src/main/native/camel.c b/dsl/camel-jbang/camel-launcher/src/main/native/camel.c index 0f2ee4c27ef31..afd8d64f0b213 100644 --- a/dsl/camel-jbang/camel-launcher/src/main/native/camel.c +++ b/dsl/camel-jbang/camel-launcher/src/main/native/camel.c @@ -1,4 +1,4 @@ -/* +/** * 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. @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /* * camel.exe - minimal WinGet-compatible bootstrap for the Camel CLI launcher. * diff --git a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java index 5aebdcbc21316..e008043d373db 100644 --- a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java +++ b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java @@ -25,17 +25,16 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertEquals; 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-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. */ @EnabledOnOs(OS.WINDOWS) class CamelExeBootstrapTest { From cc65d96ce486ca3b6182192451faae7631e87acb Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:50:13 -0400 Subject: [PATCH 09/30] CAMEL-23703: camel-launcher - build native camel.exe CI on JDK 21 Apache Camel's Maven build now enforces JDK 21+ (enforce-java-version); the Windows launcher workflow provisioned JDK 17 and failed the enforcer. Bump the CI build JDK to 21. The launcher runtime requirement is unchanged (Java 17+). Co-Authored-By: Claude Opus 4.8 --- .github/workflows/camel-launcher-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index 918ee7e89de4d..8dc38ca79a7ba 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -40,11 +40,11 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'temurin' - java-version: '17' + java-version: '21' cache: 'maven' - name: Build launcher (compiles native camel.exe) and run tests shell: cmd From 01f5f57a043dceeac69d22a6ea05114aac8b4606 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:58:14 -0400 Subject: [PATCH 10/30] CAMEL-23703: tooling/camel-exe - split native bootstrap from camel-launcher Move camel.exe build and tests into tooling/camel-exe so Windows CI can validate the native bootstrap without building the full jbang plugin tree. camel-launcher now copies the exe artifact on Windows for assembly. Co-authored-by: Cursor --- .github/workflows/camel-launcher-windows.yml | 58 ++++- dsl/camel-jbang/camel-launcher/README.md | 19 ++ dsl/camel-jbang/camel-launcher/pom.xml | 53 ++--- tooling/camel-exe/pom.xml | 209 ++++++++++++++++++ .../camel-exe}/src/main/native/README.md | 4 +- .../camel-exe}/src/main/native/camel.c | 0 .../tooling/exe}/CamelExeBootstrapTest.java | 2 +- tooling/pom.xml | 1 + 8 files changed, 316 insertions(+), 30 deletions(-) create mode 100644 tooling/camel-exe/pom.xml rename {dsl/camel-jbang/camel-launcher => tooling/camel-exe}/src/main/native/README.md (92%) rename {dsl/camel-jbang/camel-launcher => tooling/camel-exe}/src/main/native/camel.c (100%) rename {dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher => tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe}/CamelExeBootstrapTest.java (99%) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index 8dc38ca79a7ba..32cf85746c106 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -34,7 +34,61 @@ permissions: contents: read jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + camel-exe: ${{ steps.outputs.outputs.camel-exe }} + camel-launcher: ${{ steps.outputs.outputs.camel-launcher }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + if: github.event_name != 'workflow_dispatch' + with: + filters: | + camel-exe: + - 'tooling/camel-exe/**' + - '.github/workflows/camel-launcher-windows.yml' + camel-launcher: + - 'dsl/camel-jbang/**' + - 'tooling/camel-exe/**' + - '.github/workflows/camel-launcher-windows.yml' + - id: outputs + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "camel-exe=true" >> "$GITHUB_OUTPUT" + echo "camel-launcher=true" >> "$GITHUB_OUTPUT" + else + echo "camel-exe=${{ steps.filter.outputs.camel-exe }}" >> "$GITHUB_OUTPUT" + echo "camel-launcher=${{ steps.filter.outputs.camel-launcher }}" >> "$GITHUB_OUTPUT" + fi + + 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 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 @@ -46,12 +100,12 @@ jobs: distribution: 'temurin' java-version: '21' cache: 'maven' - - name: Build launcher (compiles native camel.exe) and run tests + - name: Build launcher with native camel.exe and run tests 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 dsl/camel-jbang/camel-launcher -am install -Dcamel.launcher.requireWindowsExe=true + mvn -B -ntp -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -Dcamel.launcher.requireWindowsExe=true - name: Assert archive carries bin/camel.exe shell: pwsh run: | diff --git a/dsl/camel-jbang/camel-launcher/README.md b/dsl/camel-jbang/camel-launcher/README.md index 06e2f1dd63cb9..565ba85defd75 100644 --- a/dsl/camel-jbang/camel-launcher/README.md +++ b/dsl/camel-jbang/camel-launcher/README.md @@ -16,6 +16,21 @@ 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: + +```bash +mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -Dcamel.launcher.requireWindowsExe=true +``` + +To build and test only the native bootstrap: + +```bash +mvn -pl tooling/camel-exe verify -Dcamel.exe.requireWindowsExe=true +``` + +See [`tooling/camel-exe/src/main/native/README.md`](../../../tooling/camel-exe/src/main/native/README.md) for MSVC requirements. + ## Usage ### Using the JAR directly @@ -47,8 +62,12 @@ java -jar camel-launcher-.jar run hello.java # On Windows bin\camel.bat [command] [options] + bin\camel.exe [command] [options] ``` + `camel.exe` and `camel.bat` behave identically on Windows; `camel.exe` exists for package managers + (such as WinGet) that require a genuine executable command. + ## Benefits - No need for JBang installation diff --git a/dsl/camel-jbang/camel-launcher/pom.xml b/dsl/camel-jbang/camel-launcher/pom.xml index 73f7dea0afd17..6e36324a27e38 100644 --- a/dsl/camel-jbang/camel-launcher/pom.xml +++ b/dsl/camel-jbang/camel-launcher/pom.xml @@ -328,44 +328,47 @@ - build-windows-exe + include-camel-exe windows + + + org.apache.camel + camel-exe + ${project.version} + exe + + - org.codehaus.mojo - exec-maven-plugin + org.apache.maven.plugins + maven-dependency-plugin - compile-camel-exe - prepare-package + copy-camel-exe + generate-resources - exec + copy - cl.exe - - /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 - + + + org.apache.camel + camel-exe + ${project.version} + exe + ${project.build.directory} + camel.exe + + @@ -375,8 +378,8 @@ diff --git a/tooling/camel-exe/pom.xml b/tooling/camel-exe/pom.xml new file mode 100644 index 0000000000000..8d0f87d8a02e2 --- /dev/null +++ b/tooling/camel-exe/pom.xml @@ -0,0 +1,209 @@ + + + + + 4.0.0 + + + org.apache.camel + tooling + 4.22.0-SNAPSHOT + + + camel-exe + pom + + Camel :: Exe + Native Windows camel.exe bootstrap for the Camel CLI launcher + + + false + + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-testCompile + test-compile + + testCompile + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + default-test + test + + test + + + + + + + + + + + build-windows-exe + + + windows + + + + + + org.codehaus.mojo + exec-maven-plugin + + + compile-camel-exe + generate-test-resources + + exec + + + cl.exe + + /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 + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-camel-exe + package + + attach-artifact + + + + + ${project.build.directory}/camel.exe + exe + + + + + + + + + + + + + require-windows-exe + + + camel.exe.requireWindowsExe + true + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + require-camel-exe + prepare-package + + enforce + + + + + + ${project.build.directory}/camel.exe + + + + true + + + + + + + + + diff --git a/dsl/camel-jbang/camel-launcher/src/main/native/README.md b/tooling/camel-exe/src/main/native/README.md similarity index 92% rename from dsl/camel-jbang/camel-launcher/src/main/native/README.md rename to tooling/camel-exe/src/main/native/README.md index a8f2b088f52af..11aa5d7362c07 100644 --- a/dsl/camel-jbang/camel-launcher/src/main/native/README.md +++ b/tooling/camel-exe/src/main/native/README.md @@ -20,7 +20,7 @@ Windows. It requires the Microsoft C/C++ Build Tools (`cl.exe`) on PATH — set a developer command prompt (e.g. `vcvars64.bat`) or a CI action that provisions MSVC before running Maven: - mvn -pl dsl/camel-jbang/camel-launcher package + mvn -pl tooling/camel-exe package The compiler is invoked as: @@ -32,7 +32,7 @@ builds from the same source produce byte-identical output. ## Release gate -Release builds pass `-Dcamel.launcher.requireWindowsExe=true`, which makes the +Release builds pass `-Dcamel.exe.requireWindowsExe=true`, which makes the `require-windows-exe` profile fail the build if `target/camel.exe` is absent. This prevents publishing a Windows package that cannot satisfy WinGet's portable command contract. Because MSVC cannot cross-compile from macOS/Linux, the release diff --git a/dsl/camel-jbang/camel-launcher/src/main/native/camel.c b/tooling/camel-exe/src/main/native/camel.c similarity index 100% rename from dsl/camel-jbang/camel-launcher/src/main/native/camel.c rename to tooling/camel-exe/src/main/native/camel.c diff --git a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java b/tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe/CamelExeBootstrapTest.java similarity index 99% rename from dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java rename to tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe/CamelExeBootstrapTest.java index e008043d373db..93a63d546dacf 100644 --- a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelExeBootstrapTest.java +++ b/tooling/camel-exe/src/test/java/org/apache/camel/tooling/exe/CamelExeBootstrapTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.dsl.jbang.launcher; +package org.apache.camel.tooling.exe; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/tooling/pom.xml b/tooling/pom.xml index 16fbb6fe64e45..282288030da4c 100644 --- a/tooling/pom.xml +++ b/tooling/pom.xml @@ -42,6 +42,7 @@ parent + camel-exe spi-annotations camel-tooling-model camel-util-json From ed887a279565a33e3e371d3bcd25dca379e1c11c Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:03:08 -0400 Subject: [PATCH 11/30] CAMEL-23703: tooling/camel-exe - add module README Document why the native bootstrap lives in a standalone module and how it integrates with camel-launcher. Co-authored-by: Cursor --- tooling/camel-exe/README.md | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tooling/camel-exe/README.md diff --git a/tooling/camel-exe/README.md b/tooling/camel-exe/README.md new file mode 100644 index 0000000000000..bd3bd5a8c953a --- /dev/null +++ b/tooling/camel-exe/README.md @@ -0,0 +1,45 @@ +# Camel :: Exe + +Standalone Maven module that builds the native Windows **`camel.exe`** bootstrap 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 +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. + +## Why a separate module? + +The launcher (`dsl/camel-jbang/camel-launcher`) is a fat JAR with a large upstream +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. +- **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`). + +## Build and test + +On a Windows x64 host with Microsoft C/C++ Build Tools (`cl.exe`) on PATH: + +```bash +mvn -pl tooling/camel-exe verify -Dcamel.exe.requireWindowsExe=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 install -Dcamel.launcher.requireWindowsExe=true +``` + +See [src/main/native/README.md](src/main/native/README.md) for compiler flags, MSVC setup, +and release-gate details. From 3329b9f869097b41518f29474b1fea5fee196dec Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:07:20 -0400 Subject: [PATCH 12/30] CAMEL-23703: ci - replace paths-filter with git diff path detection dorny/paths-filter caused startup_failure on fork PRs (action not allowed). Use checkout + git diff instead; validated with actionlint. Co-authored-by: Cursor --- .github/workflows/camel-launcher-windows.yml | 45 ++++++++++++-------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index 32cf85746c106..f81395a7454cc 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -37,34 +37,45 @@ jobs: detect-changes: runs-on: ubuntu-latest outputs: - camel-exe: ${{ steps.outputs.outputs.camel-exe }} - camel-launcher: ${{ steps.outputs.outputs.camel-launcher }} + 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 - - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 - id: filter - if: github.event_name != 'workflow_dispatch' - with: - filters: | - camel-exe: - - 'tooling/camel-exe/**' - - '.github/workflows/camel-launcher-windows.yml' - camel-launcher: - - 'dsl/camel-jbang/**' - - 'tooling/camel-exe/**' - - '.github/workflows/camel-launcher-windows.yml' - - id: outputs + 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 + git fetch origin "${{ github.base_ref }}" + CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD)" else - echo "camel-exe=${{ steps.filter.outputs.camel-exe }}" >> "$GITHUB_OUTPUT" - echo "camel-launcher=${{ steps.filter.outputs.camel-launcher }}" >> "$GITHUB_OUTPUT" + 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/|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' From f3a184c5b9cc14b7c8b814d0df4b65f97596c80d Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:18:43 -0400 Subject: [PATCH 13/30] CAMEL-23703: fix Windows CI for camel-exe build and Unicode test Install camel-buildtools before the targeted camel-exe verify job, and verify Unicode argument forwarding via a UTF-8 capture file instead of cmd.exe stdout which mangles non-ASCII on GitHub Actions runners. Co-authored-by: Cursor --- .github/workflows/camel-launcher-windows.yml | 3 +- .../tooling/exe/CamelExeBootstrapTest.java | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index f81395a7454cc..d721dbb132212 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -95,6 +95,7 @@ jobs: 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: @@ -116,7 +117,7 @@ jobs: 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 tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -Dcamel.launcher.requireWindowsExe=true + mvn -B -ntp -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -Dcamel.launcher.requireWindowsExe=true - name: Assert archive carries bin/camel.exe shell: pwsh run: | 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 93a63d546dacf..1ffb2f513447d 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 @@ -40,6 +40,7 @@ class CamelExeBootstrapTest { private static final Path BUILT_EXE = Paths.get("target/camel.exe"); + private static final String CAPTURED_ARGS_FILE = "camel-args.txt"; /** Writes a fake camel.bat that echoes each argument on its own line and returns exitCode. */ private Path fakeBat(Path dir, int exitCode) throws Exception { @@ -56,6 +57,37 @@ private Path fakeBat(Path dir, int exitCode) throws Exception { return bat; } + /** + * Writes a fake camel.bat that appends each forwarded argument to {@link #CAPTURED_ARGS_FILE} as UTF-8. + *

+ * Non-ASCII argument checks cannot rely on parsing process stdout: cmd.exe {@code echo} uses the console OEM + * code page on Windows CI hosts, so Unicode forwarded correctly by camel.exe is still mangled (for example + * {@code ü} becomes {@code ?}) before Java reads the pipe. Appending via PowerShell preserves the exact + * argument text in a UTF-8 file we control. + */ + private void fakeBatCapturingArgsToUtf8File(Path dir, int exitCode) throws Exception { + Path bat = dir.resolve("camel.bat"); + String body = "@echo off\r\n" + + ":loop\r\n" + + "if \"%~1\"==\"\" goto done\r\n" + + "powershell -NoProfile -Command \"" + + "[System.IO.File]::AppendAllText('%~dp0" + CAPTURED_ARGS_FILE + "', '%~1' + [char]10, " + + "[System.Text.UTF8Encoding]::new($false))\"\r\n" + + "shift\r\n" + + "goto loop\r\n" + + ":done\r\n" + + "exit /b " + exitCode + "\r\n"; + Files.writeString(bat, body, StandardCharsets.UTF_8); + } + + private List readCapturedArgs(Path dir) throws Exception { + Path file = dir.resolve(CAPTURED_ARGS_FILE); + if (!Files.exists(file)) { + return List.of(); + } + return Files.readAllLines(file, StandardCharsets.UTF_8); + } + 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"); @@ -110,12 +142,15 @@ void preservesArgumentWithSpaces(@TempDir Path dir) throws Exception { @Test void preservesUnicodeArgument(@TempDir Path dir) throws Exception { - fakeBat(dir, 0); + fakeBatCapturingArgsToUtf8File(dir, 0); Path exe = stagedExe(dir); Result r = run(exe, "run", "rüte-über.yaml"); - assertTrue(r.stdout.contains("rüte-über.yaml"), "Unicode argument must survive: " + r.stdout); + assertEquals(0, r.exit); + List captured = readCapturedArgs(dir); + assertTrue(captured.contains("run"), captured.toString()); + assertTrue(captured.contains("rüte-über.yaml"), "Unicode argument must survive: " + captured); } @Test From fdf8688061df836dc4ded13a8e2a35425b2851cd Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:19:31 -0400 Subject: [PATCH 14/30] CAMEL-23703: apply formatter to camel-exe bootstrap test Co-authored-by: Cursor --- .../apache/camel/tooling/exe/CamelExeBootstrapTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 1ffb2f513447d..b79b06810a710 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 @@ -60,10 +60,10 @@ private Path fakeBat(Path dir, int exitCode) throws Exception { /** * Writes a fake camel.bat that appends each forwarded argument to {@link #CAPTURED_ARGS_FILE} as UTF-8. *

- * Non-ASCII argument checks cannot rely on parsing process stdout: cmd.exe {@code echo} uses the console OEM - * code page on Windows CI hosts, so Unicode forwarded correctly by camel.exe is still mangled (for example - * {@code ü} becomes {@code ?}) before Java reads the pipe. Appending via PowerShell preserves the exact - * argument text in a UTF-8 file we control. + * Non-ASCII argument checks cannot rely on parsing process stdout: cmd.exe {@code echo} uses the console OEM code + * page on Windows CI hosts, so Unicode forwarded correctly by camel.exe is still mangled (for example {@code ü} + * becomes {@code ?}) before Java reads the pipe. Appending via PowerShell preserves the exact argument text in a + * UTF-8 file we control. */ private void fakeBatCapturingArgsToUtf8File(Path dir, int exitCode) throws Exception { Path bat = dir.resolve("camel.bat"); From 162f924652ff34141ea3517fc8b47bc910347382 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:43:40 -0400 Subject: [PATCH 15/30] CAMEL-23703: fix Unicode test, remove duplicate fileSet, minor improvements - Fix preservesUnicodeArgument test: pass all args at once via %* instead of per-argument %~1 expansion, which corrupts non-ASCII characters through cmd.exe OEM code page conversion - Remove duplicate examples fileSet from launcher assembly descriptor - Merge stderr into stdout in test process helper to avoid pipe deadlock - Improve MAX_PATH error message in camel.c for debuggability - Tighten CI change detection to dsl/camel-jbang/camel-launcher/ to avoid unnecessary Windows builds for unrelated jbang submodule changes Co-authored-by: Cursor --- .github/workflows/camel-launcher-windows.yml | 2 +- .../camel-launcher/src/main/assembly/bin.xml | 7 ---- tooling/camel-exe/src/main/native/camel.c | 2 +- .../tooling/exe/CamelExeBootstrapTest.java | 32 +++++++++++-------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index d721dbb132212..a6f7d3f8e38f0 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -69,7 +69,7 @@ jobs: camel_exe=true fi - if echo "$CHANGED" | grep -qE '^(dsl/camel-jbang/|tooling/camel-exe/|\.github/workflows/camel-launcher-windows\.yml)'; then + if echo "$CHANGED" | grep -qE '^(dsl/camel-jbang/camel-launcher/|tooling/camel-exe/|\.github/workflows/camel-launcher-windows\.yml)'; then camel_launcher=true fi 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 16f1d62ad1d9d..7506708d80a9f 100644 --- a/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml +++ b/dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml @@ -70,13 +70,6 @@ *.* - - ${project.basedir}/src/examples - examples - - *.* - - ${project.build.directory} bin diff --git a/tooling/camel-exe/src/main/native/camel.c b/tooling/camel-exe/src/main/native/camel.c index afd8d64f0b213..dc5959c7c5294 100644 --- a/tooling/camel-exe/src/main/native/camel.c +++ b/tooling/camel-exe/src/main/native/camel.c @@ -54,7 +54,7 @@ int wmain(void) { wchar_t exePath[MAX_PATH]; DWORD n = GetModuleFileNameW(NULL, exePath, MAX_PATH); if (n == 0 || n >= MAX_PATH) { - fwprintf(stderr, L"camel: cannot resolve launcher path\n"); + fwprintf(stderr, L"camel: launcher path exceeds MAX_PATH (%d chars) or cannot be resolved\n", MAX_PATH); return 1; } 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 b79b06810a710..ebef5c200d1aa 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 @@ -41,6 +41,7 @@ class CamelExeBootstrapTest { private static final Path BUILT_EXE = Paths.get("target/camel.exe"); private static final String CAPTURED_ARGS_FILE = "camel-args.txt"; + private static final String CAPTURE_ARGS_SCRIPT = "capture-arg.ps1"; /** Writes a fake camel.bat that echoes each argument on its own line and returns exitCode. */ private Path fakeBat(Path dir, int exitCode) throws Exception { @@ -62,21 +63,26 @@ private Path fakeBat(Path dir, int exitCode) throws Exception { *

* Non-ASCII argument checks cannot rely on parsing process stdout: cmd.exe {@code echo} uses the console OEM code * page on Windows CI hosts, so Unicode forwarded correctly by camel.exe is still mangled (for example {@code ü} - * becomes {@code ?}) before Java reads the pipe. Appending via PowerShell preserves the exact argument text in a - * UTF-8 file we control. + * becomes {@code ?}) before Java reads the pipe. We therefore capture arguments in a UTF-8 file via a helper + * PowerShell script. The batch file passes all arguments at once using {@code %*} (the raw command-line tail) + * rather than iterating with {@code %~1}, because per-argument expansion through cmd.exe's batch variable + * substitution can corrupt non-ASCII characters on certain OEM code pages. */ private void fakeBatCapturingArgsToUtf8File(Path dir, int exitCode) throws Exception { + Path captureScript = dir.resolve(CAPTURE_ARGS_SCRIPT); + String ps1 = "$outFile = $args[0]\r\n" + + "for ($i = 1; $i -lt $args.Count; $i++) {\r\n" + + " [System.IO.File]::AppendAllText($outFile,\r\n" + + " $args[$i] + [Environment]::NewLine,\r\n" + + " [System.Text.UTF8Encoding]::new($false))\r\n" + + "}\r\n" + + "exit " + exitCode + "\r\n"; + Files.writeString(captureScript, ps1, StandardCharsets.UTF_8); + Path bat = dir.resolve("camel.bat"); - String body = "@echo off\r\n" - + ":loop\r\n" - + "if \"%~1\"==\"\" goto done\r\n" - + "powershell -NoProfile -Command \"" - + "[System.IO.File]::AppendAllText('%~dp0" + CAPTURED_ARGS_FILE + "', '%~1' + [char]10, " - + "[System.Text.UTF8Encoding]::new($false))\"\r\n" - + "shift\r\n" - + "goto loop\r\n" - + ":done\r\n" - + "exit /b " + exitCode + "\r\n"; + String body = "@powershell -NoProfile -ExecutionPolicy Bypass -File \"%~dp0" + CAPTURE_ARGS_SCRIPT + "\" " + + "\"%~dp0" + CAPTURED_ARGS_FILE + "\" %*\r\n" + + "@exit /b %ERRORLEVEL%\r\n"; Files.writeString(bat, body, StandardCharsets.UTF_8); } @@ -108,7 +114,7 @@ private Result run(Path exe, String... args) throws Exception { cmd.add(a); } ProcessBuilder pb = new ProcessBuilder(cmd); - pb.redirectErrorStream(false); + pb.redirectErrorStream(true); Process p = pb.start(); String out = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8); assertTrue(p.waitFor(60, TimeUnit.SECONDS), "camel.exe did not exit in time"); From 3eadffab25976c89cd41fa01109c681e68370578 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:52:00 -0400 Subject: [PATCH 16/30] CAMEL-23703: deduplicate camel-exe READMEs Keep purpose and architecture in the module README; trim the native README to MSVC setup, compiler flags, and release-gate details only. Co-authored-by: Cursor --- tooling/camel-exe/README.md | 4 +- tooling/camel-exe/src/main/native/README.md | 45 ++++++++------------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/tooling/camel-exe/README.md b/tooling/camel-exe/README.md index bd3bd5a8c953a..cfed452c1e38f 100644 --- a/tooling/camel-exe/README.md +++ b/tooling/camel-exe/README.md @@ -41,5 +41,5 @@ Release and integration builds that produce the launcher ZIP also build this mod mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -Dcamel.launcher.requireWindowsExe=true ``` -See [src/main/native/README.md](src/main/native/README.md) for compiler flags, MSVC setup, -and release-gate details. +See [src/main/native/README.md](src/main/native/README.md) for MSVC setup, compiler +flags, and the release-gate profile. diff --git a/tooling/camel-exe/src/main/native/README.md b/tooling/camel-exe/src/main/native/README.md index 11aa5d7362c07..506b2bde8c711 100644 --- a/tooling/camel-exe/src/main/native/README.md +++ b/tooling/camel-exe/src/main/native/README.md @@ -1,39 +1,26 @@ -# camel.exe native bootstrap +# Native build (`camel.c`) -`camel.c` compiles to a minimal x64 `camel.exe` that WinGet's portable installer -exposes as the `camel` command. WinGet's portable command must be a real -executable (a `.bat`/`.cmd`/`.ps1` target produces a broken `camel.exe` symlink), -so a genuine native binary is required. +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`. -## What it does +## MSVC setup -`camel.exe` locates the `camel.bat` in the same directory, forwards the caller's -exact command-line tail (preserving Unicode and Windows quoting) to -`cmd.exe /S /C`, inherits the standard streams, and returns the child exit code. -It performs no Java discovery and no Camel option parsing; all of that lives in -`camel.bat`. +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. -## Build - -Built by the `build-windows-exe` Maven profile, which activates automatically on -Windows. It requires the Microsoft C/C++ Build Tools (`cl.exe`) on PATH — set up -a developer command prompt (e.g. `vcvars64.bat`) or a CI action that provisions -MSVC before running Maven: - - mvn -pl tooling/camel-exe package - -The compiler is invoked as: +## Compiler invocation cl /nologo /W4 /O1 /MT /Brepro /Fe:target\camel.exe src\main\native\camel.c /link /Brepro /SUBSYSTEM:CONSOLE -`/MT` links the static CRT (no runtime DLL dependency); `/Brepro` on both the -compiler and linker makes the PE header timestamp deterministic, so repeated -builds from the same source produce byte-identical output. +- `/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 ## Release gate -Release builds pass `-Dcamel.exe.requireWindowsExe=true`, which makes the -`require-windows-exe` profile fail the build if `target/camel.exe` is absent. This -prevents publishing a Windows package that cannot satisfy WinGet's portable -command contract. Because MSVC cannot cross-compile from macOS/Linux, the release -ZIP/TAR that carries `camel.exe` must be assembled on a Windows x64 host. +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. From e8e75dbe10a5f5b35c3dde5da0a7f232102347e4 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:05:42 -0400 Subject: [PATCH 17/30] CAMEL-23703: gate launcher camel.exe via release IT, drop CI job Add CamelLauncherWindowsExeIT to assert target/camel.exe is staged and bin/camel.exe is present in the Maven-assembled ZIP during release verify. Remove the camel-launcher-windows CI job; native bootstrap coverage stays in the camel-exe workflow. Co-authored-by: Cursor --- .github/workflows/camel-launcher-windows.yml | 38 +--------- dsl/camel-jbang/camel-launcher/README.md | 5 +- dsl/camel-jbang/camel-launcher/pom.xml | 12 ++++ .../launcher/CamelLauncherWindowsExeIT.java | 72 +++++++++++++++++++ tooling/camel-exe/README.md | 2 +- 5 files changed, 89 insertions(+), 40 deletions(-) create mode 100644 dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherWindowsExeIT.java diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index a6f7d3f8e38f0..b71ea5315e78d 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -15,7 +15,7 @@ # limitations under the License. # -name: Camel launcher Windows build +name: Camel exe Windows build on: push: @@ -38,7 +38,6 @@ jobs: 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: @@ -48,7 +47,6 @@ jobs: run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "camel-exe=true" >> "$GITHUB_OUTPUT" - echo "camel-launcher=true" >> "$GITHUB_OUTPUT" exit 0 fi @@ -63,18 +61,12 @@ jobs: 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 @@ -97,31 +89,3 @@ jobs: 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 and run tests - 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 -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 - $names = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName).Entries.FullName - if ($names -notcontains 'bin/camel.exe') { throw 'camel.exe missing from launcher archive' } diff --git a/dsl/camel-jbang/camel-launcher/README.md b/dsl/camel-jbang/camel-launcher/README.md index 565ba85defd75..845c8b894fe5a 100644 --- a/dsl/camel-jbang/camel-launcher/README.md +++ b/dsl/camel-jbang/camel-launcher/README.md @@ -17,10 +17,11 @@ This will create: 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: +[`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: ```bash -mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -Dcamel.launcher.requireWindowsExe=true +mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify -Dcamel.launcher.requireWindowsExe=true ``` To build and test only the native bootstrap: diff --git a/dsl/camel-jbang/camel-launcher/pom.xml b/dsl/camel-jbang/camel-launcher/pom.xml index 6e36324a27e38..6668d00051cde 100644 --- a/dsl/camel-jbang/camel-launcher/pom.xml +++ b/dsl/camel-jbang/camel-launcher/pom.xml @@ -415,6 +415,18 @@ + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + 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/CamelLauncherWindowsExeIT.java new file mode 100644 index 0000000000000..45881a72a53db --- /dev/null +++ b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherWindowsExeIT.java @@ -0,0 +1,72 @@ +/* + * 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. + */ +package org.apache.camel.dsl.jbang.launcher; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +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. + */ +@EnabledOnOs(OS.WINDOWS) +@EnabledIfSystemProperty(named = "camel.launcher.requireWindowsExe", matches = "true") +class CamelLauncherWindowsExeIT { + + private static final Path TARGET = Paths.get("target"); + private static final Path STAGED_EXE = TARGET.resolve("camel.exe"); + + @Test + void stagedCamelExeExists() { + assertTrue(Files.isRegularFile(STAGED_EXE), + "target/camel.exe must be present before packaging the launcher distribution"); + } + + @Test + void binArchiveIncludesCamelExe() 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())) { + ZipEntry entry = archive.getEntry("bin/camel.exe"); + assertNotNull(entry, "bin/camel.exe must be included in " + zip.getFileName()); + assertTrue(entry.getSize() > 0, "bin/camel.exe must not be empty"); + } + } + + private static Path findBinZip() throws IOException { + try (Stream paths = Files.list(TARGET)) { + return paths + .filter(p -> p.getFileName().toString().matches("camel-launcher-.*-bin\\.zip")) + .findFirst() + .orElse(null); + } + } +} diff --git a/tooling/camel-exe/README.md b/tooling/camel-exe/README.md index cfed452c1e38f..390463492995e 100644 --- a/tooling/camel-exe/README.md +++ b/tooling/camel-exe/README.md @@ -38,7 +38,7 @@ mvn -pl tooling/camel-exe verify -Dcamel.exe.requireWindowsExe=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 install -Dcamel.launcher.requireWindowsExe=true +mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify -Dcamel.launcher.requireWindowsExe=true ``` See [src/main/native/README.md](src/main/native/README.md) for MSVC setup, compiler From 2a2e4d1a82c5b756d2ac46fcefda68ee8ea7bae7 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:38:30 -0400 Subject: [PATCH 18/30] CAMEL-23703: harden camel.exe and CI from review feedback - Replace fixed MAX_PATH buffer with get_exe_dir() doubling loop for Windows 10+ long path support - Check _snwprintf_s and GetExitCodeProcess return values - Add failsGracefullyWhenCamelBatIsMissing test - Reduce CI fetch-depth from full clone to 50 commits - Add POM comment explaining manual test plugin wiring Co-Authored-By: Claude Opus 4.6 --- .github/workflows/camel-launcher-windows.yml | 5 +- tooling/camel-exe/pom.xml | 5 ++ tooling/camel-exe/src/main/native/camel.c | 68 +++++++++++++++---- .../tooling/exe/CamelExeBootstrapTest.java | 10 +++ 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index b71ea5315e78d..48d26b3af6e8c 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -42,7 +42,8 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - fetch-depth: 0 + # 50 commits covers any reasonable push batch; PRs fetch the base ref below. + fetch-depth: 50 - id: changes run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then @@ -51,7 +52,7 @@ jobs: fi if [ "${{ github.event_name }}" = "pull_request" ]; then - git fetch origin "${{ github.base_ref }}" + git fetch origin "${{ github.base_ref }}" --depth=1 CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD)" else CHANGED="$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}")" diff --git a/tooling/camel-exe/pom.xml b/tooling/camel-exe/pom.xml index 8d0f87d8a02e2..f05e18ea80f56 100644 --- a/tooling/camel-exe/pom.xml +++ b/tooling/camel-exe/pom.xml @@ -46,6 +46,11 @@ + diff --git a/tooling/camel-exe/src/main/native/camel.c b/tooling/camel-exe/src/main/native/camel.c index dc5959c7c5294..8f77121d10468 100644 --- a/tooling/camel-exe/src/main/native/camel.c +++ b/tooling/camel-exe/src/main/native/camel.c @@ -50,21 +50,50 @@ static LPWSTR skip_argv0(LPWSTR cmd) { return p; } -int wmain(void) { - wchar_t exePath[MAX_PATH]; - DWORD n = GetModuleFileNameW(NULL, exePath, MAX_PATH); - if (n == 0 || n >= MAX_PATH) { - fwprintf(stderr, L"camel: launcher path exceeds MAX_PATH (%d chars) or cannot be resolved\n", MAX_PATH); - return 1; +/* + * Return a heap-allocated wide string holding the directory that contains this + * exe, or NULL on failure. The caller must free() the result. Uses a doubling + * loop so paths beyond MAX_PATH (260) work on Windows 10+ with long-path + * support enabled. + */ +static wchar_t *get_exe_dir(void) { + DWORD bufSize = MAX_PATH; + wchar_t *buf = (wchar_t *) malloc(bufSize * sizeof(wchar_t)); + if (buf == NULL) { + return NULL; } - - /* Strip the exe filename, leaving the directory. */ - wchar_t *slash = wcsrchr(exePath, L'\\'); + for (;;) { + DWORD n = GetModuleFileNameW(NULL, buf, bufSize); + if (n == 0) { + free(buf); + return NULL; + } + if (n < bufSize) { + break; + } + bufSize *= 2; + wchar_t *tmp = (wchar_t *) realloc(buf, bufSize * sizeof(wchar_t)); + if (tmp == NULL) { + free(buf); + return NULL; + } + buf = tmp; + } + wchar_t *slash = wcsrchr(buf, L'\\'); if (slash == NULL) { - fwprintf(stderr, L"camel: unexpected launcher path\n"); - return 1; + free(buf); + return NULL; } *slash = L'\0'; + return buf; +} + +int wmain(void) { + wchar_t *exePath = get_exe_dir(); + if (exePath == NULL) { + fwprintf(stderr, L"camel: cannot resolve launcher directory\n"); + return 1; + } LPWSTR tail = skip_argv0(GetCommandLineW()); @@ -78,10 +107,17 @@ int wmain(void) { LPWSTR cmdline = (LPWSTR) malloc(cap * sizeof(wchar_t)); if (cmdline == NULL) { fwprintf(stderr, L"camel: out of memory\n"); + free(exePath); + return 1; + } + int ret = _snwprintf_s(cmdline, cap, _TRUNCATE, + L"cmd.exe /S /C \"\"%s\\camel.bat\" %s\"", exePath, tail); + if (ret == -1) { + fwprintf(stderr, L"camel: command line was truncated\n"); + free(cmdline); + free(exePath); return 1; } - _snwprintf_s(cmdline, cap, _TRUNCATE, - L"cmd.exe /S /C \"\"%s\\camel.bat\" %s\"", exePath, tail); STARTUPINFOW si; PROCESS_INFORMATION pi; @@ -93,14 +129,18 @@ int wmain(void) { if (!CreateProcessW(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { fwprintf(stderr, L"camel: failed to start camel.bat (error %lu)\n", GetLastError()); free(cmdline); + free(exePath); return 1; } WaitForSingleObject(pi.hProcess, INFINITE); DWORD code = 1; - GetExitCodeProcess(pi.hProcess, &code); + if (!GetExitCodeProcess(pi.hProcess, &code)) { + fwprintf(stderr, L"camel: failed to get exit code (error %lu)\n", GetLastError()); + } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); free(cmdline); + free(exePath); return (int) code; } 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 ebef5c200d1aa..8b1e23acc005d 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 @@ -181,4 +181,14 @@ void worksWhenExeDirectoryHasSpaces(@TempDir Path base) throws Exception { assertEquals(0, r.exit, r.stdout); assertTrue(r.stdout.contains("ARG=version"), r.stdout); } + + @Test + void failsGracefullyWhenCamelBatIsMissing(@TempDir Path dir) throws Exception { + Path exe = stagedExe(dir); + + Result r = run(exe, "version"); + + assertTrue(r.exit != 0, "exit code must be non-zero when camel.bat is missing"); + assertTrue(r.stdout.length() > 0, "error message must be present in output"); + } } From b5449af6c6f64360461d838806c1e44aae125b73 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:40:14 -0400 Subject: [PATCH 19/30] CAMEL-23703: apply formatter to launcher Windows exe IT Co-Authored-By: Claude Opus 4.6 --- .../camel/dsl/jbang/launcher/CamelLauncherWindowsExeIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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/CamelLauncherWindowsExeIT.java index 45881a72a53db..643ade1a48b11 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/CamelLauncherWindowsExeIT.java @@ -33,8 +33,8 @@ 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 {@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. */ @EnabledOnOs(OS.WINDOWS) @EnabledIfSystemProperty(named = "camel.launcher.requireWindowsExe", matches = "true") From f81cda01973f62ebf23c5b275d0954f92f6c3392 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:50:00 -0400 Subject: [PATCH 20/30] CAMEL-23703: add camel-launcher Windows CI job Renames the workflow, gates it on camel-launcher path changes too, and adds a job that builds the launcher with the native camel.exe and verifies bin/camel.exe is bundled in the release archive. --- .github/workflows/camel-launcher-windows.yml | 38 +++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index 48d26b3af6e8c..fb6b4dc0bfc62 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -15,7 +15,7 @@ # limitations under the License. # -name: Camel exe Windows build +name: Camel launcher Windows build on: push: @@ -38,6 +38,7 @@ jobs: 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: @@ -48,6 +49,7 @@ jobs: run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "camel-exe=true" >> "$GITHUB_OUTPUT" + echo "camel-launcher=true" >> "$GITHUB_OUTPUT" exit 0 fi @@ -62,12 +64,18 @@ jobs: 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 @@ -90,3 +98,31 @@ jobs: 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 + $names = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName).Entries.FullName + if ($names -notcontains 'bin/camel.exe') { throw 'camel.exe missing from launcher archive' } From 6fc500c4c7f0aecc6efe95d995559481d02b8338 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 09:36:01 -0400 Subject: [PATCH 21/30] CAMEL-23703: exclude non-jar artifacts from camel-launcher self-executing JAR camel-launcher depends on camel-exe:exe purely so the assembly descriptor can stage bin/camel.exe; RepackageMojo.includeArtifact() only filtered by scope, so this non-jar dependency was getting embedded as a spurious library at BOOT-INF/lib/camel-exe-*.exe inside the self-executing jar. Only jar-type artifacts are valid Spring Boot loader libraries. Found while investigating the camel-launcher-windows CI failure reported on PR #24665 (missing bin/camel.exe in the packaged archive) - a separate, confirmed defect uncovered along the way, not the fix for that failure. Co-authored-by: Claude Sonnet 5 --- .../org/apache/camel/maven/RepackageMojo.java | 8 +++- .../apache/camel/maven/RepackageMojoTest.java | 45 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) 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 3ffeec345ba47..3c2bab83feacf 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 @@ -129,7 +129,13 @@ private void getLibraries(LibraryCallback callback) throws IOException { } } - private boolean includeArtifact(Artifact artifact) { + 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. + if (!"jar".equals(artifact.getType())) { + return false; + } String scope = artifact.getScope(); // Include compile and runtime dependencies return Artifact.SCOPE_COMPILE.equals(scope) || 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 279a8aeab423d..329a43d571978 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 @@ -18,6 +18,9 @@ import java.io.File; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -61,4 +64,46 @@ public void testDependencyInclusion() throws Exception { assertTrue(true, "Placeholder test - would verify dependency inclusion"); } + + @Test + public void testCompileScopedJarIsIncluded() { + RepackageMojo mojo = new RepackageMojo(); + Artifact artifact = artifact("org.apache.camel", "camel-jbang-core", "jar", Artifact.SCOPE_COMPILE); + + assertTrue(mojo.includeArtifact(artifact), + "a compile-scoped jar dependency must be bundled into BOOT-INF/lib"); + } + + @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. + RepackageMojo mojo = new RepackageMojo(); + Artifact artifact = artifact("org.apache.camel", "camel-exe", "exe", Artifact.SCOPE_COMPILE); + + assertFalse(mojo.includeArtifact(artifact), + "a non-jar artifact (e.g. the native camel-exe:exe bootstrap) must not be bundled into BOOT-INF/lib"); + } + + @Test + public void testProvidedCamelJarIsIncluded() { + RepackageMojo mojo = new RepackageMojo(); + Artifact artifact = artifact("org.apache.camel", "camel-util", "jar", Artifact.SCOPE_PROVIDED); + + assertTrue(mojo.includeArtifact(artifact), + "a provided-scope org.apache.camel jar dependency must still be bundled"); + } + + @Test + public void testProvidedNonCamelJarIsExcluded() { + RepackageMojo mojo = new RepackageMojo(); + Artifact artifact = artifact("org.jolokia", "jolokia-agent-jvm", "jar", Artifact.SCOPE_PROVIDED); + + assertFalse(mojo.includeArtifact(artifact), + "a provided-scope non-Camel jar dependency must not be bundled"); + } + + private static Artifact artifact(String groupId, String artifactId, String type, String scope) { + return new DefaultArtifact(groupId, artifactId, "1.0", scope, type, null, new DefaultArtifactHandler(type)); + } } From a6c1d7a61a2e8e3d1d39366c67ade7cedce5c45e Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 09:36:18 -0400 Subject: [PATCH 22/30] CAMEL-23703: add temporary Defender diagnostics to camel-launcher-windows PR #24665 review (davsclaus) reported the camel-launcher-windows job building successfully but the packaged archive missing bin/camel.exe. Investigation confirmed the dependency:copy -> enforcer -> assembly plumbing is correct: target/camel.exe exists on disk at prepare-package (enforcer passes), and an isolated reproduction of the exact Maven/ assembly-descriptor setup reliably produces the file in the archive outside this CI environment. The disappearance is therefore specific to the Windows runner, between the enforcer check and archive creation. Add a non-gating diagnostic step that checks whether target/camel.exe still exists post-build and inspects Windows Defender exclusions, threat detections, and the operational event log, to confirm or rule out AV quarantine of the freshly-compiled, unsigned executable before committing to a fix. Remove once the next CI run gives an answer. Co-authored-by: Claude Sonnet 5 --- .github/workflows/camel-launcher-windows.yml | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index fb6b4dc0bfc62..6513d1b43f3d3 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -119,6 +119,45 @@ jobs: 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: Diagnose camel.exe disappearance (temporary, CAMEL-23703 CI investigation) + if: always() + shell: pwsh + run: | + Write-Host '--- target/camel.exe on disk after the full build ---' + $exe = 'dsl/camel-jbang/camel-launcher/target/camel.exe' + if (Test-Path $exe) { + Get-Item $exe | Select-Object FullName, Length, CreationTime, LastWriteTime + } else { + Write-Host "MISSING: $exe does not exist after the build completed" + } + + Write-Host '--- dsl/camel-jbang/camel-launcher/target listing ---' + Get-ChildItem dsl/camel-jbang/camel-launcher/target | Format-Table Name, Length, LastWriteTime + + Write-Host '--- Windows Defender exclusions ---' + try { + Get-MpPreference | Select-Object -ExpandProperty ExclusionPath + } catch { + Write-Host "Get-MpPreference failed: $_" + } + + Write-Host '--- Windows Defender threat detections ---' + try { + Get-MpThreatDetection | Format-Table ThreatID, Resources, InitialDetectionTime + } catch { + Write-Host "Get-MpThreatDetection failed or found nothing: $_" + } + + Write-Host '--- Windows Defender operational log (last 30 minutes, detection/action events) ---' + try { + Get-WinEvent -FilterHashtable @{ + LogName = 'Microsoft-Windows-Windows Defender/Operational' + Id = 1116, 1117 + StartTime = (Get-Date).AddMinutes(-30) + } | Format-Table TimeCreated, Id, Message -Wrap + } catch { + Write-Host "Get-WinEvent found no matching Defender events: $_" + } - name: Assert archive carries bin/camel.exe shell: pwsh run: | From c9effd8511aa25fae58aaccf15d636efbaf5d1e6 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:49:15 -0400 Subject: [PATCH 23/30] CAMEL-23703: expand CI diagnostics for the missing bin/camel.exe The Defender diagnostic added previously ruled out AV quarantine: target/camel.exe was confirmed present and untouched immediately after the build, with zero Defender detections or exclusions. So whatever drops it happens inside assembly:single itself while writing the archive, not afterward. Add two more temporary diagnostic steps: - Full entry listing for both the zip and tar.gz (previously only the zip's presence/absence of a single entry was checked - this shows whether tar.gz is also missing it, and the actual entry path shape). - A standalone re-run of assembly:single with -X debug logging against the already-populated target/ dir (reusing the completed build's output rather than re-running the whole ~620-module reactor), to see what the DirectoryScanner/archiver actually did with the camel.exe fileSet. Co-authored-by: Claude Sonnet 5 --- .github/workflows/camel-launcher-windows.yml | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index 6513d1b43f3d3..a41dd2299f8b3 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -158,6 +158,37 @@ jobs: } catch { Write-Host "Get-WinEvent found no matching Defender events: $_" } + - name: List archive entries (temporary, CAMEL-23703 CI investigation) + if: always() + shell: pwsh + run: | + Write-Host '--- zip entries (full listing) ---' + $zip = Get-ChildItem dsl/camel-jbang/camel-launcher/target/camel-launcher-*-bin.zip | Select-Object -First 1 + if ($zip) { + Add-Type -AssemblyName System.IO.Compression.FileSystem + $reader = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName) + $reader.Entries.FullName | Sort-Object + $reader.Dispose() + } else { + Write-Host "No zip archive found" + } + + Write-Host '' + Write-Host '--- tar.gz entries (full listing) ---' + $tarball = Get-ChildItem dsl/camel-jbang/camel-launcher/target/camel-launcher-*-bin.tar.gz | Select-Object -First 1 + if ($tarball) { + tar -tzf $tarball.FullName | Sort-Object + } else { + Write-Host "No tar.gz archive found" + } + - name: Re-run assembly:single with debug logging (temporary, CAMEL-23703 CI investigation) + if: always() + shell: pwsh + run: | + mvn -B -ntp -pl dsl/camel-jbang/camel-launcher org.apache.maven.plugins:maven-assembly-plugin:3.8.0:single -X *> assembly-debug.log + Write-Host '--- Debug log lines mentioning camel.exe, the fileSet scan, or the archiver ---' + Select-String -Path assembly-debug.log -Pattern 'camel\.exe|fileSet|DirectoryScanner|Cannot access|Scanning|addFileSet|Adding|Skipping' | + ForEach-Object { $_.Line } - name: Assert archive carries bin/camel.exe shell: pwsh run: | From 915ba2c3c9b99c960ab0e4d667377008bc9e278c Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:08:25 -0400 Subject: [PATCH 24/30] CAMEL-23703: build camel-launcher-windows with mvnd for parallel reactor camel-launcher's dependency closure is ~620 modules (it bundles camel-jbang-core plus several jbang plugins and kamelets), and the Windows job built them with plain sequential mvn (no -T), taking ~23 minutes for that step alone. Switch to mvnd (Apache's Maven daemon), which the project already uses for incremental PR builds on Linux, so the reactor builds in parallel instead of one module at a time. mvnd's windows-amd64 distribution is confirmed available from Apache downloads, but this is the first time this repo runs mvnd on a Windows runner: the build step moves from `shell: cmd` to `shell: bash` so the MSVC environment set by vcvars64.bat can be captured via `set` and re-exported for mvnd's daemon process, since cmd's `call` only affects the current cmd process. Not wired into the camel-exe job (its reactor is small enough that sequential mvn is not the bottleneck there). NOT YET VERIFIED on an actual Windows runner - intentionally held back from push until the current camel.exe / Windows Defender diagnostic investigation on this same job concludes, to avoid confounding that result with an unrelated build-tooling change. Co-authored-by: Claude Sonnet 5 --- .github/workflows/camel-launcher-windows.yml | 27 ++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index a41dd2299f8b3..a4e75f02cddf9 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -113,12 +113,29 @@ 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 + with: + distribution: 'windows-amd64' + # Runs in bash (not cmd, like the other steps in this workflow) so vcvars64's + # environment can be captured once via `set` and exported for mvnd - cmd's `call` + # only affects the current cmd process, and mvnd needs that MSVC PATH/INCLUDE/LIB + # to reach cl.exe from the exec-maven-plugin execution in tooling/camel-exe. + - name: Build launcher with native camel.exe (mvnd) + 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,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -DskipTests -Dcamel.launcher.requireWindowsExe=true + set -euo pipefail + VSINSTALL=$("/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" \ + -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) + VCVARS_ENV=$(cmd //c "call \"$VSINSTALL\\VC\\Auxiliary\\Build\\vcvars64.bat\" && set") + while IFS='=' read -r name value; do + [ -n "$name" ] || continue + export "$name=$value" + done <<< "$VCVARS_ENV" + + "${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd" -B -ntp -Dmvnd.threads=2 \ + -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install \ + -DskipTests -Dcamel.launcher.requireWindowsExe=true - name: Diagnose camel.exe disappearance (temporary, CAMEL-23703 CI investigation) if: always() shell: pwsh From a42301adc0900c62c80beca416bfb46490bab4e8 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:04:05 -0400 Subject: [PATCH 25/30] CAMEL-23703: fix cmd quoting for vcvars64 capture in mvnd step The single quoted "call ... && set" string round-tripped through Git Bash's argv quoting (which backslash-escapes embedded quotes) and cmd.exe's /C parsing (which treats \" literally instead of unescaping it), corrupting the vcvars64.bat path and failing every run of this step on CI. Passing call/path/&&/set as separate argv elements avoids the mismatch since only the space-containing path needs quoting. --- .github/workflows/camel-launcher-windows.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index a4e75f02cddf9..0ca67ee17f6c7 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -127,7 +127,12 @@ jobs: set -euo pipefail VSINSTALL=$("/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" \ -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) - VCVARS_ENV=$(cmd //c "call \"$VSINSTALL\\VC\\Auxiliary\\Build\\vcvars64.bat\" && set") + # A single quoted "call ... && set" string round-trips through Git Bash's argv + # quoting (which backslash-escapes embedded quotes) and cmd.exe's /C parsing + # (which does not understand that escaping) mismatched, corrupting the path. + # Passing call/path/&&/set as separate argv elements sidesteps the mismatch: + # only the space-containing path needs quoting, and it has no embedded quotes. + VCVARS_ENV=$(cmd //c call "$VSINSTALL\VC\Auxiliary\Build\vcvars64.bat" "&&" set) while IFS='=' read -r name value; do [ -n "$name" ] || continue export "$name=$value" From 95c38a9881fe8bca50712bc6c551a8685c063c62 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:24:10 -0400 Subject: [PATCH 26/30] CAMEL-23703: revert camel-launcher-windows to plain mvn build The mvnd wiring introduced Windows shell plumbing (bash env-porting of vcvars64 into mvnd) that kept failing on GHA Git Bash and, because the build never finished, blocked the temporary diagnostics that inspect the missing bin/camel.exe. mvnd is a build-speed optimization that is orthogonal to that packaging bug, so restore the known-good cmd + mvn step that built and packaged successfully, and revisit mvnd-on-Windows as a separate change. --- .github/workflows/camel-launcher-windows.yml | 32 +++----------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index 0ca67ee17f6c7..a41dd2299f8b3 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -113,34 +113,12 @@ jobs: distribution: 'temurin' java-version: '21' cache: 'maven' - - id: install-mvnd - uses: ./.github/actions/install-mvnd - with: - distribution: 'windows-amd64' - # Runs in bash (not cmd, like the other steps in this workflow) so vcvars64's - # environment can be captured once via `set` and exported for mvnd - cmd's `call` - # only affects the current cmd process, and mvnd needs that MSVC PATH/INCLUDE/LIB - # to reach cl.exe from the exec-maven-plugin execution in tooling/camel-exe. - - name: Build launcher with native camel.exe (mvnd) - shell: bash + - name: Build launcher with native camel.exe + shell: cmd run: | - set -euo pipefail - VSINSTALL=$("/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" \ - -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) - # A single quoted "call ... && set" string round-trips through Git Bash's argv - # quoting (which backslash-escapes embedded quotes) and cmd.exe's /C parsing - # (which does not understand that escaping) mismatched, corrupting the path. - # Passing call/path/&&/set as separate argv elements sidesteps the mismatch: - # only the space-containing path needs quoting, and it has no embedded quotes. - VCVARS_ENV=$(cmd //c call "$VSINSTALL\VC\Auxiliary\Build\vcvars64.bat" "&&" set) - while IFS='=' read -r name value; do - [ -n "$name" ] || continue - export "$name=$value" - done <<< "$VCVARS_ENV" - - "${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd" -B -ntp -Dmvnd.threads=2 \ - -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install \ - -DskipTests -Dcamel.launcher.requireWindowsExe=true + 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: Diagnose camel.exe disappearance (temporary, CAMEL-23703 CI investigation) if: always() shell: pwsh From 3182ecd51297df5e36490a51ec9f7031da227512 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:04:48 -0400 Subject: [PATCH 27/30] CAMEL-23703: fix bin/camel.exe archive assertion to match assembly base dir maven-assembly-plugin's bin descriptor defaults includeBaseDirectory to true, so every entry is nested under camel-launcher-/ (e.g. camel-launcher-4.22.0-SNAPSHOT/bin/camel.exe). The CI assertion checked for the literal string bin/camel.exe, which never matched that prefixed path, so it failed even though camel.exe was correctly present in every build. Also drops the temporary Defender/archive-listing/debug-assembly diagnostics added while chasing this false failure. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/camel-launcher-windows.yml | 72 +------------------- 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index a41dd2299f8b3..f13855b592efb 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -119,80 +119,10 @@ jobs: 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: Diagnose camel.exe disappearance (temporary, CAMEL-23703 CI investigation) - if: always() - shell: pwsh - run: | - Write-Host '--- target/camel.exe on disk after the full build ---' - $exe = 'dsl/camel-jbang/camel-launcher/target/camel.exe' - if (Test-Path $exe) { - Get-Item $exe | Select-Object FullName, Length, CreationTime, LastWriteTime - } else { - Write-Host "MISSING: $exe does not exist after the build completed" - } - - Write-Host '--- dsl/camel-jbang/camel-launcher/target listing ---' - Get-ChildItem dsl/camel-jbang/camel-launcher/target | Format-Table Name, Length, LastWriteTime - - Write-Host '--- Windows Defender exclusions ---' - try { - Get-MpPreference | Select-Object -ExpandProperty ExclusionPath - } catch { - Write-Host "Get-MpPreference failed: $_" - } - - Write-Host '--- Windows Defender threat detections ---' - try { - Get-MpThreatDetection | Format-Table ThreatID, Resources, InitialDetectionTime - } catch { - Write-Host "Get-MpThreatDetection failed or found nothing: $_" - } - - Write-Host '--- Windows Defender operational log (last 30 minutes, detection/action events) ---' - try { - Get-WinEvent -FilterHashtable @{ - LogName = 'Microsoft-Windows-Windows Defender/Operational' - Id = 1116, 1117 - StartTime = (Get-Date).AddMinutes(-30) - } | Format-Table TimeCreated, Id, Message -Wrap - } catch { - Write-Host "Get-WinEvent found no matching Defender events: $_" - } - - name: List archive entries (temporary, CAMEL-23703 CI investigation) - if: always() - shell: pwsh - run: | - Write-Host '--- zip entries (full listing) ---' - $zip = Get-ChildItem dsl/camel-jbang/camel-launcher/target/camel-launcher-*-bin.zip | Select-Object -First 1 - if ($zip) { - Add-Type -AssemblyName System.IO.Compression.FileSystem - $reader = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName) - $reader.Entries.FullName | Sort-Object - $reader.Dispose() - } else { - Write-Host "No zip archive found" - } - - Write-Host '' - Write-Host '--- tar.gz entries (full listing) ---' - $tarball = Get-ChildItem dsl/camel-jbang/camel-launcher/target/camel-launcher-*-bin.tar.gz | Select-Object -First 1 - if ($tarball) { - tar -tzf $tarball.FullName | Sort-Object - } else { - Write-Host "No tar.gz archive found" - } - - name: Re-run assembly:single with debug logging (temporary, CAMEL-23703 CI investigation) - if: always() - shell: pwsh - run: | - mvn -B -ntp -pl dsl/camel-jbang/camel-launcher org.apache.maven.plugins:maven-assembly-plugin:3.8.0:single -X *> assembly-debug.log - Write-Host '--- Debug log lines mentioning camel.exe, the fileSet scan, or the archiver ---' - Select-String -Path assembly-debug.log -Pattern 'camel\.exe|fileSet|DirectoryScanner|Cannot access|Scanning|addFileSet|Adding|Skipping' | - ForEach-Object { $_.Line } - 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 $names = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName).Entries.FullName - if ($names -notcontains 'bin/camel.exe') { throw 'camel.exe missing from launcher archive' } + if (-not ($names -like '*/bin/camel.exe')) { throw 'camel.exe missing from launcher archive' } From f76de4f1c0e353899013012897945be3a18c2572 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:54:06 -0400 Subject: [PATCH 28/30] CAMEL-23703: fix exact-path zip lookup in CamelLauncherWindowsExeIT archive.getEntry("bin/camel.exe") used an exact path, but the assembly descriptor does not set includeBaseDirectory to false, so entries are nested under camel-launcher-/ and the lookup always returned null. Match on a "/bin/camel.exe" suffix instead, mirroring the fix already applied to the CI PowerShell assertion. Co-Authored-By: Claude Sonnet 5 --- .../dsl/jbang/launcher/CamelLauncherWindowsExeIT.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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/CamelLauncherWindowsExeIT.java index 643ade1a48b11..c2108f78fa20c 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/CamelLauncherWindowsExeIT.java @@ -55,7 +55,12 @@ void binArchiveIncludesCamelExe() throws IOException { assertNotNull(zip, "camel-launcher-*-bin.zip must be produced by the assembly plugin"); try (ZipFile archive = new ZipFile(zip.toFile())) { - ZipEntry entry = archive.getEntry("bin/camel.exe"); + // 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")) + .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"); } From 8134495c17b7b7f907ec44ca07dfe95051722572 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:52:31 -0400 Subject: [PATCH 29/30] CAMEL-23703: address review feedback on PR #24665 Dispose the ZipArchive returned by ZipFile.OpenRead in the launcher CI workflow instead of leaking the handle, and drain camel.exe's stdout on a background thread in CamelExeBootstrapTest so the 60s waitFor timeout can actually fire (and the process gets destroyed) if the exe under test hangs, instead of blocking forever on readAllBytes waiting for the pipe to close. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/camel-launcher-windows.yml | 9 +++-- .../tooling/exe/CamelExeBootstrapTest.java | 35 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/workflows/camel-launcher-windows.yml b/.github/workflows/camel-launcher-windows.yml index f13855b592efb..d6a9cca151769 100644 --- a/.github/workflows/camel-launcher-windows.yml +++ b/.github/workflows/camel-launcher-windows.yml @@ -124,5 +124,10 @@ jobs: 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 - $names = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName).Entries.FullName - if (-not ($names -like '*/bin/camel.exe')) { throw 'camel.exe missing from launcher archive' } + $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/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 8b1e23acc005d..3171012393c3b 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 @@ -16,6 +16,8 @@ */ package org.apache.camel.tooling.exe; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -116,12 +118,33 @@ private Result run(Path exe, String... args) throws Exception { ProcessBuilder pb = new ProcessBuilder(cmd); pb.redirectErrorStream(true); Process p = pb.start(); - String out = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8); - assertTrue(p.waitFor(60, TimeUnit.SECONDS), "camel.exe did not exit in time"); - Result r = new Result(); - r.exit = p.exitValue(); - r.stdout = out; - return r; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Drain stdout on a background thread: reading it inline would block until the pipe closes + // at process exit, making the waitFor timeout below unreachable if camel.exe hangs. + Thread drain = new Thread(() -> { + try { + p.getInputStream().transferTo(out); + } catch (IOException e) { + // stream closes abruptly when the process is destroyed on timeout below + } + }); + drain.start(); + try { + boolean exited = p.waitFor(60, TimeUnit.SECONDS); + if (!exited) { + p.destroyForcibly(); + } + assertTrue(exited, "camel.exe did not exit in time"); + drain.join(TimeUnit.SECONDS.toMillis(5)); + Result r = new Result(); + r.exit = p.exitValue(); + r.stdout = out.toString(StandardCharsets.UTF_8); + return r; + } finally { + if (p.isAlive()) { + p.destroyForcibly(); + } + } } @Test From b7ab7bed6a1b32305cdc3c6e5cb0c2ad42552dfa Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Wed, 15 Jul 2026 07:58:33 +0200 Subject: [PATCH 30/30] Update docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc --- .../modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 18f4722a27f25..e58517c676e49 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 @@ -86,9 +86,7 @@ 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 -command (such as WinGet's portable installer) can expose `camel` directly. Direct -users may continue to invoke `bin\camel.bat`; both behave identically. Windows -ARM64 and x86 are not yet supported. +users may continue to invoke `bin\camel.bat`; both behave identically. === camel-langchain4j-agent