This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 2d2be71458a8 CAMEL-26435: camel-jbang - The CLI starts log4j for the
self-update check, and runs java -version before each command (#28765)
2d2be71458a8 is described below
commit 2d2be71458a83f0fdc379703bde3f64ed2041f45
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 31 18:09:57 2026 +0100
CAMEL-26435: camel-jbang - The CLI starts log4j for the self-update check,
and runs java -version before each command (#28765)
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../dsl/jbang/core/common/InstallDetector.java | 16 +--
.../src/main/resources/bin/camel.bat | 8 ++
.../camel-launcher/src/main/resources/bin/camel.sh | 12 ++-
.../dsl/jbang/launcher/CamelBatDiscoveryTest.java | 34 +++++++
.../dsl/jbang/launcher/CamelShDiscoveryTest.java | 51 ++++++++++
.../jbang/launcher/LauncherStartupLoggingTest.java | 111 +++++++++++++++++++++
6 files changed, 224 insertions(+), 8 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/InstallDetector.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/InstallDetector.java
index 2ecaa429672e..1d13aadcb87b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/InstallDetector.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/InstallDetector.java
@@ -24,11 +24,10 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Optional;
import java.util.function.Predicate;
-import org.apache.camel.util.FileUtil;
-
/**
* Detects where the currently running Camel CLI launcher lives, and discovers
other Camel CLI installations on the same
* machine, across the web installer and the package managers the launcher is
also distributed through (Homebrew,
@@ -51,6 +50,11 @@ public final class InstallDetector {
public record InstallInfo(Path location, InstallMethod method) {
}
+ // not FileUtil.isWindows(): FileUtil has a logger, and the self-update
check runs this before each command of the
+ // launcher, where creating a logger would configure log4j
+ private static final boolean IS_WINDOWS
+ = System.getProperty("os.name",
"").toLowerCase(Locale.ROOT).startsWith("windows");
+
private InstallDetector() {
}
@@ -102,7 +106,7 @@ public final class InstallDetector {
* {@code %LOCALAPPDATA%\Apache Camel\cli\versions} on Windows.
*/
public static Path webInstallerVersionsRoot() {
- if (FileUtil.isWindows()) {
+ if (IS_WINDOWS) {
return Paths.get(System.getenv("LOCALAPPDATA"), "Apache Camel",
"cli", "versions");
}
String xdgDataHome = System.getenv("XDG_DATA_HOME");
@@ -160,7 +164,7 @@ public final class InstallDetector {
public static List<InstallInfo> scanKnownLocations() {
List<InstallInfo> found = new ArrayList<>();
scanWebInstaller(found);
- if (FileUtil.isWindows()) {
+ if (IS_WINDOWS) {
scanChocolatey(found);
scanScoopWindows(found);
} else {
@@ -173,7 +177,7 @@ public final class InstallDetector {
private static void scanWebInstaller(List<InstallInfo> found) {
Path root = webInstallerVersionsRoot();
- listVersionDirs(root, dir ->
Files.exists(dir.resolve("bin").resolve(FileUtil.isWindows() ? "camel.bat" :
"camel.sh")))
+ listVersionDirs(root, dir ->
Files.exists(dir.resolve("bin").resolve(IS_WINDOWS ? "camel.bat" : "camel.sh")))
.forEach(dir -> found.add(new InstallInfo(dir,
InstallMethod.WEB_INSTALLER)));
}
@@ -240,7 +244,7 @@ public final class InstallDetector {
if (path == null || path.isBlank()) {
return Optional.empty();
}
- String exeName = FileUtil.isWindows() ? "camel.cmd" : "camel";
+ String exeName = IS_WINDOWS ? "camel.cmd" : "camel";
for (String dir : path.split(File.pathSeparator)) {
if (dir.isBlank()) {
continue;
diff --git a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat
b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat
index 2df7eeaa70b5..83d883c93c6e 100644
--- a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat
+++ b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat
@@ -89,6 +89,12 @@ exit /B 1
set "_CAND=%~1"
if "%_CAND%"=="" exit /b 1
if not exist "%_CAND%" exit /b 1
+@REM The release file of the JDK (bin\..\release) has the version, which saves
starting a JVM for -version.
+set "_RAW="
+for %%d in ("%_CAND%") do set "_RELEASE=%%~dpd..\release"
+if exist "%_RELEASE%" for /f "tokens=2 delims==" %%v in ('findstr /b /l
/c:"JAVA_VERSION=" "%_RELEASE%"') do if not defined _RAW set "_RAW=%%v"
+set "_RELEASE="
+if defined _RAW goto tryJavaParseVersion
for %%e in ("%_CAND%") do set "_CAND_EXT=%%~xe"
set "_PROBE_DIR="
if defined TEMP set "_PROBE_DIR=%TEMP%"
@@ -123,6 +129,8 @@ for /f "tokens=3" %%v in ('findstr /b /l /i /c:"java
version " /c:"openjdk versi
del /q "%_PROBE%" >NUL 2>&1
set "_PROBE="
if not defined _RAW exit /b 1
+
+:tryJavaParseVersion
set "_RAW=%_RAW:"=%"
set "_MAJOR="
for /f "tokens=1,2 delims=." %%a in ("%_RAW%") do (
diff --git a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh
b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh
index f38fa2edbe37..aac01cf18f02 100644
--- a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh
+++ b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh
@@ -55,9 +55,17 @@ fi
CAMEL_MIN_JAVA=21
# Echo the Java major version of the launcher in $1, or nothing if it cannot
be determined.
+# The release file of the JDK (bin/../release) has the version, which saves
starting a JVM for -version.
camel_java_major() {
- _cjm_out=`"$1" -version </dev/null 2>&1` || return 1
- _cjm_ver=`echo "$_cjm_out" | sed -n 's/.*version "\([0-9][0-9.]*\).*/\1/p' |
head -n 1`
+ _cjm_ver=
+ _cjm_release=`dirname "$1"`/../release
+ if [ -f "$_cjm_release" ]; then
+ _cjm_ver=`sed -n 's/^JAVA_VERSION="\([0-9][0-9.]*\).*/\1/p'
"$_cjm_release" | head -n 1`
+ fi
+ if [ -z "$_cjm_ver" ]; then
+ _cjm_out=`"$1" -version </dev/null 2>&1` || return 1
+ _cjm_ver=`echo "$_cjm_out" | sed -n 's/.*version "\([0-9][0-9.]*\).*/\1/p'
| head -n 1`
+ fi
[ -n "$_cjm_ver" ] || return 1
case "$_cjm_ver" in
1.*) echo "$_cjm_ver" | cut -d. -f2 ;;
diff --git
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
index e78134ad0ddc..a3a0a2dda757 100644
---
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
+++
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
@@ -233,6 +233,40 @@ class CamelBatDiscoveryTest {
assertFalse(r.stdout().contains("RAN=OLD"), r.stdout());
}
+ @Test
+ void readsTheVersionFromTheReleaseFileOfTheJdk(@TempDir Path base) throws
Exception {
+ // the banner of -version is not a Java version: the version comes
from the release file, without -version
+ Path h = home(base);
+ Path java = FakeJava.writeFakeJava(base.resolve("jdk").resolve("bin"),
"java", GARBAGE, 0, "RELEASE");
+ Files.writeString(base.resolve("jdk").resolve("release"),
"IMPLEMENTOR=\"Test\"\r\nJAVA_VERSION=\"21.0.3\"\r\n");
+ Map<String, String> env = isolatedEnvironment();
+ env.put("JAVACMD", java.toString());
+ env.put("JAVA_HOME", base.resolve("invalid-java-home").toString());
+
+ FakeJava.Result r = FakeJava.run(h.resolve("camel.bat"), env,
"version");
+
+ assertEquals(0, r.exitCode(), r.stderr());
+ assertTrue(r.stdout().contains("RAN=RELEASE"), r.stdout());
+ }
+
+ @Test
+ void rejectsAnOldJavaFromTheReleaseFileOfTheJdk(@TempDir Path base) throws
Exception {
+ Path h = home(base);
+ Path old = FakeJava.writeFakeJava(base.resolve("jdk").resolve("bin"),
"java", JAVA21, 0, "OLD");
+ Files.writeString(base.resolve("jdk").resolve("release"),
"JAVA_VERSION=\"17.0.2\"\r\n");
+ Path fallback = FakeJava.writeFakeJava(base.resolve("fallback"),
"java", JAVA21, 0, "FALLBACK");
+ Map<String, String> env = isolatedEnvironment();
+ env.put("JAVACMD", old.toString());
+ env.put("JAVA_HOME", base.resolve("invalid-java-home").toString());
+ env.put("CAMEL_FALLBACK_JAVA", fallback.toString());
+
+ FakeJava.Result r = FakeJava.run(h.resolve("camel.bat"), env,
"version");
+
+ assertEquals(0, r.exitCode(), r.stderr());
+ assertTrue(r.stdout().contains("RAN=FALLBACK"), r.stdout());
+ assertFalse(r.stdout().contains("RAN=OLD"), r.stdout());
+ }
+
@Test
void rejectsWhenNoCandidateQualifies(@TempDir Path base) throws Exception {
Path h = home(base);
diff --git
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
index 71f15c200677..c3413e75c929 100644
---
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
+++
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
@@ -312,6 +312,57 @@ class CamelShDiscoveryTest {
assertEquals(1, r.exitCode(), "Java 8 must be rejected: " +
r.stdout());
}
+ @Test
+ void readsTheVersionFromTheReleaseFileOfTheJdk(@TempDir Path base) throws
Exception {
+ // the banner of -version is not a Java version: the version comes
from the release file, without -version
+ Path h = home(base);
+ Path java = FakeJava.writeFakeJava(base.resolve("jdk/bin"), "java",
GARBAGE, 0, "RELEASE");
+ Files.writeString(base.resolve("jdk/release"),
"IMPLEMENTOR=\"Test\"\nJAVA_VERSION=\"21.0.3\"\n");
+
+ Map<String, String> env = new HashMap<>();
+ env.put("JAVACMD", java.toString());
+ env.put("PATH", pathWithNonqualifyingJava(base));
+
+ FakeJava.Result r = FakeJava.run(h.resolve("camel.sh"), env,
"version");
+
+ assertEquals(0, r.exitCode(), r.stderr());
+ assertTrue(r.stdout().contains("RAN=RELEASE"), r.stdout());
+ }
+
+ @Test
+ void rejectsAnOldJavaFromTheReleaseFileOfTheJdk(@TempDir Path base) throws
Exception {
+ Path h = home(base);
+ Path old = FakeJava.writeFakeJava(base.resolve("jdk/bin"), "java",
JAVA21, 0, "OLD");
+ Files.writeString(base.resolve("jdk/release"),
"JAVA_VERSION=\"17.0.2\"\n");
+ Path pathDir = base.resolve("pd");
+ FakeJava.writeFakeJava(pathDir, "java", JAVA21, 0, "PATH");
+
+ Map<String, String> env = new HashMap<>();
+ env.put("JAVACMD", old.toString());
+ env.put("PATH", pathDir + ":/usr/bin:/bin");
+
+ FakeJava.Result r = FakeJava.run(h.resolve("camel.sh"), env,
"version");
+
+ assertEquals(0, r.exitCode(), r.stderr());
+ assertTrue(r.stdout().contains("RAN=PATH"), r.stdout());
+ }
+
+ @Test
+ void runsJavaVersionWhenTheReleaseFileHasNoVersion(@TempDir Path base)
throws Exception {
+ Path h = home(base);
+ Path java = FakeJava.writeFakeJava(base.resolve("jdk/bin"), "java",
JAVA21, 0, "BANNER");
+ Files.writeString(base.resolve("jdk/release"),
"IMPLEMENTOR=\"Test\"\n");
+
+ Map<String, String> env = new HashMap<>();
+ env.put("JAVACMD", java.toString());
+ env.put("PATH", pathWithNonqualifyingJava(base));
+
+ FakeJava.Result r = FakeJava.run(h.resolve("camel.sh"), env,
"version");
+
+ assertEquals(0, r.exitCode(), r.stderr());
+ assertTrue(r.stdout().contains("RAN=BANNER"), r.stdout());
+ }
+
@Test
void handlesSpacesAndUnicodeInJavaPath(@TempDir Path base) throws
Exception {
Path h = home(base);
diff --git
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/LauncherStartupLoggingTest.java
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/LauncherStartupLoggingTest.java
new file mode 100644
index 000000000000..c4d837270610
--- /dev/null
+++
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/LauncherStartupLoggingTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.dsl.jbang.core.common.InstallDetector;
+import org.apache.camel.dsl.jbang.core.common.LauncherHelper;
+import org.apache.camel.dsl.jbang.launcher.selfupdate.UpdateChecker;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A command of the launcher that logs nothing does not configure log4j, which
takes about 140 ms: it creates no logger
+ * (creating one configures log4j) and turns the logging off without
configuring log4j. Each case runs in a new JVM and
+ * lists the classes it loads.
+ */
+class LauncherStartupLoggingTest {
+
+ @TempDir
+ Path dir;
+
+ @Test
+ void camelVersionDoesNotConfigureLog4j() throws Exception {
+ List<String> classes = run(CamelLauncher.class.getName(), "version");
+
+ assertThat(classes).contains(UpdateChecker.class.getName(),
InstallDetector.class.getName());
+ assertNoLogging(classes);
+ }
+
+ @Test
+ void theUpdateCheckOfTheWebInstallerDoesNotConfigureLog4j() throws
Exception {
+ List<String> classes = run(WebInstallerUpdateCheck.class.getName());
+
+ // the catalog tells the running version, after the check found the
web installer
+ assertThat(classes).contains(DefaultCamelCatalog.class.getName());
+ assertNoLogging(classes);
+ }
+
+ private static void assertNoLogging(List<String> classes) {
+ assertThat(classes.stream().filter(c ->
c.startsWith("org.slf4j."))).as("no logger").isEmpty();
+ assertThat(classes.stream().filter(c ->
c.startsWith("org.apache.logging.log4j.core.appender.")))
+ .as("log4j is not configured").isEmpty();
+ }
+
+ // the classes the JVM loads
+ private List<String> run(String mainClass, String... args) throws
Exception {
+ String java = Path.of(System.getProperty("java.home"), "bin",
"java").toString();
+ List<String> command = new ArrayList<>(
+ List.of(java, "-Xlog:class+load=info:file=classes.log:none",
"-cp", System.getProperty("java.class.path"),
+ "-Duser.home=" + dir, mainClass));
+ command.addAll(List.of(args));
+ ProcessBuilder builder = new ProcessBuilder(command)
+ .directory(dir.toFile())
+ .redirectErrorStream(true)
+ .redirectOutput(dir.resolve("out.txt").toFile());
+ Map<String, String> env = builder.environment();
+ env.remove("CAMEL_SELF_UPDATE_CHECK");
+ // where the web installer installs (POSIX and Windows)
+ env.put("XDG_DATA_HOME", dir.toString());
+ env.put("LOCALAPPDATA", dir.toString());
+ Process process = builder.start();
+ assertThat(process.waitFor(60, TimeUnit.SECONDS)).isTrue();
+ assertThat(process.exitValue()).as("exit code, output: " +
Files.readString(dir.resolve("out.txt"))).isZero();
+ List<String> classes = new ArrayList<>();
+ for (String line : Files.readAllLines(dir.resolve("classes.log"),
StandardCharsets.UTF_8)) {
+ classes.add(line.split(" ")[0]);
+ }
+ return classes;
+ }
+
+ /**
+ * The update check of a launcher installed by the web installer.
+ */
+ public static final class WebInstallerUpdateCheck {
+
+ public static void main(String... args) throws Exception {
+ Path root = InstallDetector.webInstallerVersionsRoot();
+ // a cache that is up to date, so that the check does not go to
the network
+ Path cache = root.getParent().resolve("update-check.properties");
+ Files.createDirectories(cache.getParent());
+ Files.writeString(cache, "last_checked=" +
System.currentTimeMillis() + "\nlatest_version=4.0.0\n");
+ Path jar =
root.resolve("4.0.0").resolve("lib").resolve("camel-launcher.jar");
+ System.setProperty(LauncherHelper.CAMEL_LAUNCHER_JAR_PROPERTY,
jar.toString());
+ UpdateChecker.maybeNotify(new String[] { "version" });
+ }
+ }
+}