This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24661 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 57d616e7b17a40976331fecce3ef9097f0afdb94 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Sep 17 09:16:44 2026 +0200 CAMEL-24661: camel-jbang - camel run --runtime=quarkus|spring-boot passes the execution limits and names the Quarkus log file after the application The exported Quarkus and Spring Boot runs only carried --jvm-args and --jfr to the child JVM, so --max-seconds, --max-messages and --max-idle-seconds were ignored (the existing-project path and Camel Main already passed them). The camel.main.durationMax* properties are now built once and used by all the run paths. The Quarkus run also named its log file after the --name option, which defaults to CamelJBang, while the export derives the application name from the first route file and the application reports that name. camel log and the TUI Log tab look for <reported name>.log and found nothing. The name is now read back from the exported application.properties as the Camel Main run already does. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../modules/ROOT/pages/camel-jbang-running.adoc | 6 ++ .../apache/camel/dsl/jbang/core/commands/Run.java | 73 ++++++++++------ .../core/commands/RunExportedProjectTest.java | 96 ++++++++++++++++++++++ 3 files changed, 148 insertions(+), 27 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc index 64017405c2aa..ea0b9443b16b 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc @@ -258,6 +258,12 @@ Spring Boot runs via `spring-boot:run`, and Quarkus via `quarkus:dev` (or `quark This is the same JVM you would get from `camel export`, and is what the Camel TUI uses when launching examples and folders. +The options `--profile`, `--port`, `--prop`, `--max-seconds`, `--max-messages`, `--max-idle-seconds`, +`--jvm-args` and `--jfr` are passed to the application on all three runtimes. The application logs to a +file in `~/.camel` so `camel log` and the TUI can read the logs: `<pid>.log` for Spring Boot, and +`<name>.log` for Quarkus and Camel Main (`<name>` is the name the application reports, from `--name` or +else the first route file). + Limitations compared to the `jbang` runtime: - Startup is slower, as the project must be built with Maven first (the first run also downloads diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java index 3a69b153d406..33cf67066483 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java @@ -1464,6 +1464,38 @@ public class Run extends CamelCommand { return existing.trim() + " " + extra; } + /** + * The execution limits ({@code --max-seconds}, {@code --max-messages} and {@code --max-idle-seconds}) as + * {@code camel.main.durationMax*} system properties, which all the runtimes honour. + */ + List<String> buildDurationLimitArgs() { + List<String> args = new ArrayList<>(); + if (executionLimitOptions.maxSeconds > 0) { + args.add("-Dcamel.main.durationMaxSeconds=" + executionLimitOptions.maxSeconds); + } + if (executionLimitOptions.maxMessages > 0) { + args.add("-Dcamel.main.durationMaxMessages=" + executionLimitOptions.maxMessages); + } + if (executionLimitOptions.maxIdleSeconds > 0) { + args.add("-Dcamel.main.durationMaxIdleSeconds=" + executionLimitOptions.maxIdleSeconds); + } + return args; + } + + /** + * The JVM arguments the exported Quarkus or Spring Boot project is run with ({@code jvm.args} and + * {@code spring-boot.run.jvmArguments}): {@code --jvm-args}, the flight recording and the execution limits. Returns + * null when there are none. + */ + String buildExportedRunJvmArgs() { + String args = mergeJvmArgs(jvmArgs, buildJfrJvmArgs()); + List<String> limits = buildDurationLimitArgs(); + if (!limits.isEmpty()) { + args = mergeJvmArgs(args, String.join(" ", limits)); + } + return args != null && !args.isBlank() ? args.trim() : null; + } + protected int runQuarkus() throws Exception { if (background) { printer().printErr("Run Camel Quarkus with --background is not supported"); @@ -1568,7 +1600,10 @@ public class Run extends CamelCommand { return exit; } - appNameRef.set(eq.name); + // the exported project may have derived the application name from the source files, and the log file + // must be named after the name the application reports (which is what camel log and the TUI look for) + String appName = resolveExportedAppName(runDirPath, eq.name); + appNameRef.set(appName); // prepare quarkus for logging to file Path appProps = Paths.get(eq.exportDir, "src/main/resources/application.properties"); @@ -1576,7 +1611,7 @@ public class Run extends CamelCommand { String content = Files.readString(appProps); content += "\n# logging to file\n" + "quarkus.log.file.enabled=true\n" - + "quarkus.log.file.path=${user.home}/.camel/" + eq.name + ".log\n" + + "quarkus.log.file.path=${user.home}/.camel/" + appName + ".log\n" + "quarkus.log.file.format=" + QUARKUS_LOG_FILE_FORMAT + "\n"; Files.writeString(appProps, content); } @@ -1592,9 +1627,9 @@ public class Run extends CamelCommand { mvnCmd.add("--quiet"); mvnCmd.add("--file"); mvnCmd.add(runDirPath.toRealPath().resolve("pom.xml").toString()); - String quarkusRunJvmArgs = mergeJvmArgs(jvmArgs, buildJfrJvmArgs()); - if (quarkusRunJvmArgs != null && !quarkusRunJvmArgs.isBlank()) { - mvnCmd.add("-Djvm.args=" + quarkusRunJvmArgs.trim()); + String quarkusRunJvmArgs = buildExportedRunJvmArgs(); + if (quarkusRunJvmArgs != null) { + mvnCmd.add("-Djvm.args=" + quarkusRunJvmArgs); } mvnCmd.add("package"); mvnCmd.add("quarkus:" + (dev ? "dev" : "run")); @@ -1793,15 +1828,7 @@ public class Run extends CamelCommand { if (dev) { javaCmd.addAll(buildCamelMainReloadArgs()); } - if (executionLimitOptions.maxSeconds > 0) { - javaCmd.add("-Dcamel.main.durationMaxSeconds=" + executionLimitOptions.maxSeconds); - } - if (executionLimitOptions.maxMessages > 0) { - javaCmd.add("-Dcamel.main.durationMaxMessages=" + executionLimitOptions.maxMessages); - } - if (executionLimitOptions.maxIdleSeconds > 0) { - javaCmd.add("-Dcamel.main.durationMaxIdleSeconds=" + executionLimitOptions.maxIdleSeconds); - } + javaCmd.addAll(buildDurationLimitArgs()); javaCmd.add("-jar"); javaCmd.add(jar.toAbsolutePath().toString()); @@ -1820,7 +1847,7 @@ public class Run extends CamelCommand { * The application name as configured in the exported project ({@code camel.main.name}), which is what the running * application reports to the CLI and the TUI. */ - private static String resolveExportedAppName(Path runDirPath, String fallback) { + static String resolveExportedAppName(Path runDirPath, String fallback) { Path props = runDirPath.resolve("src/main/resources/application.properties"); if (Files.exists(props)) { try (InputStream is = Files.newInputStream(props)) { @@ -1864,15 +1891,7 @@ public class Run extends CamelCommand { if (serverOptions.port != -1) { args.add("-D" + portKey + "=" + serverOptions.port); } - if (executionLimitOptions.maxSeconds > 0) { - args.add("-Dcamel.main.durationMaxSeconds=" + executionLimitOptions.maxSeconds); - } - if (executionLimitOptions.maxMessages > 0) { - args.add("-Dcamel.main.durationMaxMessages=" + executionLimitOptions.maxMessages); - } - if (executionLimitOptions.maxIdleSeconds > 0) { - args.add("-Dcamel.main.durationMaxIdleSeconds=" + executionLimitOptions.maxIdleSeconds); - } + args.addAll(buildDurationLimitArgs()); if (property != null) { for (String p : property) { String s = p.trim(); @@ -2328,9 +2347,9 @@ public class Run extends CamelCommand { mvnCmd.add("--quiet"); mvnCmd.add("--file"); mvnCmd.add(runDirPath.toRealPath().resolve("pom.xml").toString()); - String springBootRunJvmArgs = mergeJvmArgs(jvmArgs, buildJfrJvmArgs()); - if (springBootRunJvmArgs != null && !springBootRunJvmArgs.isBlank()) { - mvnCmd.add("-Dspring-boot.run.jvmArguments=" + springBootRunJvmArgs.trim()); + String springBootRunJvmArgs = buildExportedRunJvmArgs(); + if (springBootRunJvmArgs != null) { + mvnCmd.add("-Dspring-boot.run.jvmArguments=" + springBootRunJvmArgs); } mvnCmd.add("spring-boot:run"); pb.command(mvnCmd); diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportedProjectTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportedProjectTest.java new file mode 100644 index 000000000000..35722e0da8e8 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportedProjectTest.java @@ -0,0 +1,96 @@ +/* + * 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.core.commands; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies how {@code camel run --runtime=quarkus|spring-boot} runs the exported project: the execution limits are + * passed to the child JVM like for Camel Main, and the log file is named after the name the application reports. + */ +class RunExportedProjectTest extends CamelCommandBaseTestSupport { + + @TempDir + Path dir; + + private static Run run(String... args) { + Run command = new Run(new CamelJBangMain()); + CommandLine.populateCommand(command, args); + return command; + } + + @Test + void durationLimitsArePassedToExportedRun() { + String args = run("--max-seconds=30", "--max-messages=5", "--max-idle-seconds=10", "route.yaml") + .buildExportedRunJvmArgs(); + + assertThat(args).isEqualTo("-Dcamel.main.durationMaxSeconds=30 -Dcamel.main.durationMaxMessages=5" + + " -Dcamel.main.durationMaxIdleSeconds=10"); + } + + @Test + void durationLimitsAreMergedWithJvmArgs() { + String args = run("--jvm-args=-Xmx512m", "--max-seconds=30", "route.yaml").buildExportedRunJvmArgs(); + + assertThat(args).isEqualTo("-Xmx512m -Dcamel.main.durationMaxSeconds=30"); + } + + @Test + void noJvmArgsWhenNothingToPass() { + assertThat(run("route.yaml").buildExportedRunJvmArgs()).isNull(); + } + + @Test + void jfrIsPassedAsJvmArgument() { + String args = run("--jfr", "route.yaml").buildExportedRunJvmArgs(); + + assertThat(args).contains("-XX:StartFlightRecording"); + } + + @Test + void durationLimitsAreTheSameForAllRuntimes() { + Run run = run("--max-seconds=30", "route.yaml"); + + List<String> limits = run.buildDurationLimitArgs(); + assertThat(limits).containsExactly("-Dcamel.main.durationMaxSeconds=30"); + assertThat(run.buildExistingProjectSystemProperties("server.port")).containsAll(limits); + assertThat(run.buildExportedRunJvmArgs()).isEqualTo(String.join(" ", limits)); + } + + @Test + void appNameIsTheExportedCamelMainName() throws Exception { + Path resources = dir.resolve("src/main/resources"); + Files.createDirectories(resources); + Files.writeString(resources.resolve("application.properties"), "camel.main.name=my-route\n"); + + assertThat(Run.resolveExportedAppName(dir, "CamelJBang")).isEqualTo("my-route"); + } + + @Test + void appNameFallsBackWhenNotExported() { + assertThat(Run.resolveExportedAppName(dir, "CamelJBang")).isEqualTo("CamelJBang"); + } + +}
