This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-26435-cli-startup-log4j
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 86506a6dc31aef1c2e1a4ebfd8df72a574f93bea
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 31 10:48:04 2026 +0100

    CAMEL-26435: camel-jbang - The CLI starts log4j for the self-update check, 
and runs java -version before each command
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[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" });
+        }
+    }
+}

Reply via email to