Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3dd73b0
CAMEL-23703: camel-launcher - native x64 camel.exe bootstrap source
ammachado Jul 12, 2026
290e488
CAMEL-23703: camel-launcher - document native camel.exe build
ammachado Jul 12, 2026
27ac26d
CAMEL-23703: camel-launcher - Maven profiles to build and gate native…
ammachado Jul 12, 2026
cf4bb4a
CAMEL-23703: camel-launcher - include native camel.exe in bin archives
ammachado Jul 12, 2026
6d4aaf2
CAMEL-23703: camel-launcher - Windows behavioral test for native came…
ammachado Jul 12, 2026
0dd0340
CAMEL-23703: ci - build and test native camel.exe on Windows x64
ammachado Jul 12, 2026
9606fb6
CAMEL-23703: docs - native camel.exe note in 4.22 upgrade guide
ammachado Jul 12, 2026
177b0ee
CAMEL-23703: camel-launcher - apply formatter and impsort
ammachado Jul 12, 2026
cc65d96
CAMEL-23703: camel-launcher - build native camel.exe CI on JDK 21
ammachado Jul 13, 2026
01f5f57
CAMEL-23703: tooling/camel-exe - split native bootstrap from camel-la…
ammachado Jul 13, 2026
ed887a2
CAMEL-23703: tooling/camel-exe - add module README
ammachado Jul 13, 2026
3329b9f
CAMEL-23703: ci - replace paths-filter with git diff path detection
ammachado Jul 13, 2026
f3a184c
CAMEL-23703: fix Windows CI for camel-exe build and Unicode test
ammachado Jul 13, 2026
fdf8688
CAMEL-23703: apply formatter to camel-exe bootstrap test
ammachado Jul 13, 2026
162f924
CAMEL-23703: fix Unicode test, remove duplicate fileSet, minor improv…
ammachado Jul 13, 2026
3eadffa
CAMEL-23703: deduplicate camel-exe READMEs
ammachado Jul 13, 2026
e8e75db
CAMEL-23703: gate launcher camel.exe via release IT, drop CI job
ammachado Jul 13, 2026
2a2e4d1
CAMEL-23703: harden camel.exe and CI from review feedback
ammachado Jul 13, 2026
b5449af
CAMEL-23703: apply formatter to launcher Windows exe IT
ammachado Jul 13, 2026
f81cda0
CAMEL-23703: add camel-launcher Windows CI job
ammachado Jul 13, 2026
6fc500c
CAMEL-23703: exclude non-jar artifacts from camel-launcher self-execu…
ammachado Jul 14, 2026
a6c1d7a
CAMEL-23703: add temporary Defender diagnostics to camel-launcher-win…
ammachado Jul 14, 2026
c9effd8
CAMEL-23703: expand CI diagnostics for the missing bin/camel.exe
ammachado Jul 14, 2026
915ba2c
CAMEL-23703: build camel-launcher-windows with mvnd for parallel reactor
ammachado Jul 14, 2026
a42301a
CAMEL-23703: fix cmd quoting for vcvars64 capture in mvnd step
ammachado Jul 14, 2026
95c38a9
CAMEL-23703: revert camel-launcher-windows to plain mvn build
ammachado Jul 14, 2026
3182ecd
CAMEL-23703: fix bin/camel.exe archive assertion to match assembly ba…
ammachado Jul 14, 2026
f76de4f
CAMEL-23703: fix exact-path zip lookup in CamelLauncherWindowsExeIT
ammachado Jul 14, 2026
8134495
CAMEL-23703: address review feedback on PR #24665
ammachado Jul 15, 2026
b7ab7be
Update docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_2…
davsclaus Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions .github/workflows/camel-launcher-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#
# 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:
detect-changes:
runs-on: ubuntu-latest
outputs:
camel-exe: ${{ steps.changes.outputs.camel-exe }}
camel-launcher: ${{ steps.changes.outputs.camel-launcher }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# 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
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 }}" --depth=1
CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD)"
else
CHANGED="$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}")"
fi

echo "Changed files:"
echo "$CHANGED"

camel_exe=false
camel_launcher=false

if echo "$CHANGED" | grep -qE '^(tooling/camel-exe/|\.github/workflows/camel-launcher-windows\.yml)'; then
camel_exe=true
fi

if echo "$CHANGED" | grep -qE '^(dsl/camel-jbang/camel-launcher/|tooling/camel-exe/|\.github/workflows/camel-launcher-windows\.yml)'; then
camel_launcher=true
fi

echo "camel-exe=$camel_exe" >> "$GITHUB_OUTPUT"
echo "camel-launcher=$camel_launcher" >> "$GITHUB_OUTPUT"

camel-exe:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-exe == 'true'
runs-on: windows-2022
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
- name: Build and test native camel.exe
shell: cmd
run: |
for /f "usebackq delims=" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
call "%VSINSTALL%\VC\Auxiliary\Build\vcvars64.bat"
mvn -B -ntp -pl buildingtools install -DskipTests
mvn -B -ntp -pl tooling/camel-exe verify -Dcamel.exe.requireWindowsExe=true

camel-launcher-windows:
needs: detect-changes
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.camel-launcher == 'true'
runs-on: windows-2022
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
- name: Build launcher with native camel.exe
shell: cmd
run: |
for /f "usebackq delims=" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
call "%VSINSTALL%\VC\Auxiliary\Build\vcvars64.bat"
mvn -B -ntp -pl buildingtools,tooling/camel-exe,dsl/camel-jbang/camel-launcher -am install -DskipTests -Dcamel.launcher.requireWindowsExe=true
- name: Assert archive carries bin/camel.exe
shell: pwsh
run: |
$zip = Get-ChildItem dsl/camel-jbang/camel-launcher/target/camel-launcher-*-bin.zip | Select-Object -First 1
Add-Type -AssemblyName System.IO.Compression.FileSystem
$archive = [System.IO.Compression.ZipFile]::OpenRead($zip.FullName)
try {
$names = $archive.Entries.FullName
if (-not ($names -like '*/bin/camel.exe')) { throw 'camel.exe missing from launcher archive' }
} finally {
$archive.Dispose()
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ 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
users may continue to invoke `bin\camel.bat`; both behave identically.

=== camel-langchain4j-agent

The `Agent.chat()` method return type has changed from `String` to `Result<String>` (from `dev.langchain4j.service.Result`).
Expand Down
20 changes: 20 additions & 0 deletions dsl/camel-jbang/camel-launcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ This will create:
1. A self-executing JAR (`camel-launcher-<version>.jar`) in the `target` directory using Spring Boot's nested JAR structure
2. Distribution archives (`camel-launcher-<version>-bin.zip` and `camel-launcher-<version>-bin.tar.gz`) in the `target` directory

On Windows, the distribution archives also include `bin/camel.exe`, a native bootstrap built by
[`tooling/camel-exe`](../../../tooling/camel-exe). Release builds on a Windows x64 host run an integration test during `verify` that asserts
`target/camel.exe` is staged and `bin/camel.exe` is present in the assembled ZIP:

```bash
mvn -pl tooling/camel-exe,dsl/camel-jbang/camel-launcher -am verify -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
Expand Down Expand Up @@ -47,8 +63,12 @@ java -jar camel-launcher-<version>.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
Expand Down
106 changes: 106 additions & 0 deletions dsl/camel-jbang/camel-launcher/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,110 @@
</plugin>
</plugins>
</build>

<profiles>
<!--
On Windows, copy the native camel.exe artifact from tooling/camel-exe into target/
so the assembly descriptor can include it in bin/camel.exe.
-->
<profile>
<id>include-camel-exe</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-exe</artifactId>
<version>${project.version}</version>
<type>exe</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-camel-exe</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.apache.camel</groupId>
<artifactId>camel-exe</artifactId>
<version>${project.version}</version>
<type>exe</type>
<outputDirectory>${project.build.directory}</outputDirectory>
<destFileName>camel.exe</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

<!--
Release gate: fails the build before packaging if camel.exe was not copied into
target/. Enabled only when -Dcamel.launcher.requireWindowsExe=true (release job on
a Windows x64 host), so ordinary cross-platform dev builds are unaffected.
-->
<profile>
<id>require-windows-exe</id>
<activation>
<property>
<name>camel.launcher.requireWindowsExe</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>require-camel-exe</id>
<phase>prepare-package</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireFilesExist>
<files>
<file>${project.build.directory}/camel.exe</file>
</files>
</requireFilesExist>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
8 changes: 8 additions & 0 deletions dsl/camel-jbang/camel-launcher/src/main/assembly/bin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,13 @@
<include>*.*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>camel.exe</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
</assembly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] archive.getEntry("bin/camel.exe") uses an exact path, but the assembly descriptor (bin.xml) does not set <includeBaseDirectory>false</includeBaseDirectory> — Maven Assembly Plugin defaults to true, which prefixes entries with camel-launcher-VERSION-bin/. This means getEntry("bin/camel.exe") would return null.

The CI PowerShell assertion handles this correctly with wildcard matching (*/bin/camel.exe). Consider iterating entries with a suffix match here as well, or setting includeBaseDirectory to false in the assembly descriptor if that's the intended layout.

This test is double-gated (@EnabledOnOs(OS.WINDOWS) + @EnabledIfSystemProperty) so it doesn't affect CI, but would fail for a developer running mvn verify -Dcamel.launcher.requireWindowsExe=true on Windows.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, same root cause as the CI assertion bug, fixed in f76de4f (suffix match instead of exact bin/camel.exe lookup, since includeBaseDirectory defaults to true in the assembly descriptor).

While digging into this I found a bigger gap worth flagging: the official release pipeline (Jenkinsfile.deploy) runs on ubuntu, and build-windows-exe / include-camel-exe only activate via <os><family>windows</family>. Neither camel.exe.requireWindowsExe nor camel.launcher.requireWindowsExe is set anywhere in that pipeline, so on an actual release the native compile is skipped, the camel.exe fileSet in bin.xml matches nothing, and the released camel-launcher-*-bin.zip ships without camel.exe, no error, just silently missing. That would break the Winget packaging from the other PR.

@davsclaus since you know the release process better than I do: is there already a place a Windows-hosted build step could hook into (e.g. a separate agent on the release Jenkins job), or should this be a post-release GitHub Actions job that builds the Windows artifact and uploads it alongside the rest of the release? Happy to put a PR together once there's a direction.

Claude Code on behalf of @ammachado
AI-generated review, so it may get things wrong. Please check any suggestion before you act on it.

Path zip = findBinZip();
assertNotNull(zip, "camel-launcher-*-bin.zip must be produced by the assembly plugin");

try (ZipFile archive = new ZipFile(zip.toFile())) {
// maven-assembly-plugin defaults includeBaseDirectory to true, so entries are
// nested under camel-launcher-<version>/, 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");
}
}

private static Path findBinZip() throws IOException {
try (Stream<Path> paths = Files.list(TARGET)) {
return paths
.filter(p -> p.getFileName().toString().matches("camel-launcher-.*-bin\\.zip"))
.findFirst()
.orElse(null);
}
}
}
Loading