This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-4.10.x in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.10.x by this push: new 796052a7730 CAMEL-21729: camel-jbang: dependency update pom.xml that can automati… (#17089) 796052a7730 is described below commit 796052a77302e9581e90590ff03b60dffe8a6bdc Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Fri Feb 7 14:41:05 2025 +0100 CAMEL-21729: camel-jbang: dependency update pom.xml that can automati… (#17089) * CAMEL-21729: camel-jbang: dependency update pom.xml that can automatic add new Camel JARs --- .../ROOT/pages/camel-4x-upgrade-guide-4_10.adoc | 5 + .../modules/ROOT/pages/camel-jbang.adoc | 22 ++- .../dsl/jbang/core/commands/DependencyList.java | 9 + .../dsl/jbang/core/commands/DependencyUpdate.java | 206 +++++++++++++++++++-- .../org/apache/camel/tooling/maven/MavenGav.java | 23 +++ 5 files changed, 246 insertions(+), 19 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc index 3c34504a565..7698380408f 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc @@ -193,6 +193,11 @@ The `camel-jgroups-cluster-service-starter` in Camel Spring Boot has been remove The camel-jbang commands for `camel-k` has been removed. +The `camel dependency update` has removed the option `--source` to specify the source file, +but to refer to the source file directly such as: + +`camel dependency update --source=MyRoute.java` to be `camel dependency update MyRoute.java`. + === camel-micrometer We have fixed a flawed behavior when using dynamic endpoints which made the generation of endpoint events to grow in an uncontrolled way. From now on the component will generate events for the endpoint base URI as a default behavior. If you still want to collect events for the extended URI (including the parameters), then, you can use the `camel.metrics.baseEndpointURIExchangeEventNotifier=false` configuration. Mind that this is strongly discouraged as it can make your number of events g [...] diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc index f54a3abccf3..c1db7087040 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc @@ -2644,7 +2644,7 @@ public class foo extends EndpointRouteBuilder { } ---- -TIP: You can use `camel dependency update --source=foo.java` to update the dependencies. +TIP: You can use `camel dependency update foo.java` to update the dependencies. Notice how we specify https://www.jbang.dev/documentation/guide/latest/dependencies.html[JBang dependencies] at the top of the file. We want JBang to know and prepare for the IDE of choice. @@ -2666,7 +2666,7 @@ When working with Java source code, then you can keep the JBang dependencies up [source,bash] ---- -$ camel dependency update --source=foo.java +$ camel dependency update foo.java ---- TIP: You can use `--clean` to not keep any existing dependencies and generate a clean fresh list. @@ -2676,6 +2676,24 @@ This will then automatic insert or update the JBang dependencies (`//DEPS`) in t You may want to use this for making it easier to load the source into an IDE editor to do coding. See the previous section for more details. +===== Updating dependencies in Maven projects + +Camel JBang can also help with keeping Camel Maven dependencies up-to-date for Maven based projects. + +For example if start using new Camel components, in your Camel routes. Then you would have to +add the corresponding Camel JAR dependencies to the pom.xml file. + +You can use the `camel dependency update` command to automate this, by executing from the project folder: + +[source,bash] +---- +$ camel dependency update pom.xml +---- + +Notice, that only adding new Camel dependencies is supported. If you remove a component, +then you need to remove the dependency from the pom.xml manually. +Also, this is only intended for production code from `src/main` folder. + ==== Camel route debugging using VSCode or IDEA editors The Camel route debugger is available by default (the `camel-debug` component is automatically added to the classpath). By default, it can be reached through JMX at the URL `service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi/camel`. diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyList.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyList.java index 2c4565a4e4c..c2c8a3d4ae6 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyList.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyList.java @@ -88,6 +88,15 @@ public class DependencyList extends Export { String quarkusVersion = null; for (int i = 0; i < nl.getLength(); i++) { Element node = (Element) nl.item(i); + + // must be child at <project/dependencyManagement> or <project/dependencies> + String p = node.getParentNode().getNodeName(); + String p2 = node.getParentNode().getParentNode().getNodeName(); + boolean accept = "project".equals(p2) && (p.equals("dependencyManagement") || p.equals("dependencies")); + if (!accept) { + continue; + } + String g = node.getElementsByTagName("groupId").item(0).getTextContent(); String a = node.getElementsByTagName("artifactId").item(0).getTextContent(); String v = null; diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java index d4b8f0dbf33..9557f21abe0 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java @@ -17,31 +17,41 @@ package org.apache.camel.dsl.jbang.core.commands; import java.io.File; +import java.io.FileInputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.StringJoiner; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.apache.camel.tooling.maven.MavenGav; import org.apache.camel.util.IOHelper; +import org.apache.camel.util.StringHelper; +import org.apache.camel.util.xml.XmlLineNumberParser; +import org.apache.logging.log4j.util.Strings; import picocli.CommandLine; @CommandLine.Command(name = "update", - description = "Updates JBang style dependencies in source file", sortOptions = false, + description = "Updates dependencies in Maven pom.xml or Java source file (JBang style)", + sortOptions = false, showDefaultValues = true) public class DependencyUpdate extends DependencyList { - @CommandLine.Option(names = { "--source" }, - description = "Camel source such as .java file to have dependencies updated (//DEPS)", - required = true) - protected String source; + @CommandLine.Parameters(description = "Maven pom.xml or Java source files (JBang Style with //DEPS) to have dependencies updated", + arity = "1") + public File file; @CommandLine.Option(names = { "--clean" }, - description = "Regenerate list of dependencies (do not keep existing dependencies)") + description = "Regenerate list of dependencies (do not keep existing dependencies). Not supported for pom.xml") protected boolean clean; private final List<String> deps = new ArrayList<>(); - private File target; + private final List<MavenGav> gavs = new ArrayList<>(); public DependencyUpdate(CamelJBangMain main) { super(main); @@ -50,15 +60,16 @@ public class DependencyUpdate extends DependencyList { @Override public Integer doCall() throws Exception { // source file must exist - target = new File(source); - if (!target.exists()) { - printer().printErr("Source file does not exist: " + target); + if (!file.exists()) { + printer().printErr("Source file does not exist: " + file); return -1; } - if (clean) { + boolean maven = "pom.xml".equals(file.getName()); + + if (clean && !maven) { // remove DEPS in source file first - updateSource(); + updateJBangSource(); } return super.doCall(); @@ -66,6 +77,28 @@ public class DependencyUpdate extends DependencyList { @Override protected void outputGav(MavenGav gav, int index, int total) { + try { + boolean maven = "pom.xml".equals(file.getName()); + if (maven) { + outputGavMaven(gav, index, total); + } else { + outputGavJBang(gav, index, total); + } + } catch (Exception e) { + printer().printErr("Cannot update dependencies due to " + e.getMessage(), e); + } + } + + protected void outputGavMaven(MavenGav gav, int index, int total) throws Exception { + gavs.add(gav); + + boolean last = total - index <= 1; + if (last) { + updateMavenSource(); + } + } + + protected void outputGavJBang(MavenGav gav, int index, int total) { if (index == 0) { deps.add("//DEPS org.apache.camel:camel-bom:" + gav.getVersion() + "@pom"); } @@ -79,13 +112,13 @@ public class DependencyUpdate extends DependencyList { } boolean last = total - index <= 1; if (last) { - updateSource(); + updateJBangSource(); } } - private void updateSource() { + private void updateJBangSource() { try { - List<String> lines = Files.readAllLines(target.toPath()); + List<String> lines = Files.readAllLines(file.toPath()); List<String> answer = new ArrayList<>(); // find position of where the old DEPS was @@ -117,9 +150,148 @@ public class DependencyUpdate extends DependencyList { } String text = String.join(System.lineSeparator(), answer); - IOHelper.writeText(text, target); + IOHelper.writeText(text, file); } catch (Exception e) { - printer().printErr("Error updating source file: " + target + " due to: " + e.getMessage()); + printer().printErr("Error updating source file: " + file + " due to: " + e.getMessage()); + } + } + + private void updateMavenSource() throws Exception { + List<MavenGav> existingGavs = new ArrayList<>(); + + Node camelClone = null; + int targetLineNumber = -1; + + File pom = new File(file.getName()); + if (pom.exists()) { + // use line number parser as we want to find where to add new Camel JARs after the existing Camel JARs + Document dom = XmlLineNumberParser.parseXml(new FileInputStream(pom)); + String camelVersion = null; + NodeList nl = dom.getElementsByTagName("dependency"); + for (int i = 0; i < nl.getLength(); i++) { + Element node = (Element) nl.item(i); + + // must be child at <project/dependencyManagement> or <project/dependencies> + String p = node.getParentNode().getNodeName(); + String p2 = node.getParentNode().getParentNode().getNodeName(); + boolean accept = "project".equals(p2) && (p.equals("dependencyManagement") || p.equals("dependencies")); + if (!accept) { + continue; + } + + String g = node.getElementsByTagName("groupId").item(0).getTextContent(); + String a = node.getElementsByTagName("artifactId").item(0).getTextContent(); + String v = null; + NodeList vl = node.getElementsByTagName("version"); + if (vl.getLength() > 0) { + v = vl.item(0).getTextContent(); + } + String scope = null; + vl = node.getElementsByTagName("scope"); + if (vl.getLength() > 0) { + scope = vl.item(0).getTextContent(); + } + if (scope != null && !"compile".equals(scope)) { + continue; + } + + if ("org.apache.camel".equals(g)) { + camelVersion = v; + if (camelClone == null && !"camel-bom".equals(a)) { + camelClone = node.cloneNode(true); + } + String num = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END); + if (num != null) { + targetLineNumber = Integer.parseInt(num); + } + num = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END); + if (num != null) { + targetLineNumber = Integer.parseInt(num); + } + } + if ("org.apache.camel.springboot".equals(g)) { + camelVersion = v; + String num = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END); + if (num != null) { + targetLineNumber = Integer.parseInt(num); + } + } + if ("org.apache.camel.quarkus".equals(g)) { + String num = (String) node.getUserData(XmlLineNumberParser.LINE_NUMBER_END); + if (num != null) { + targetLineNumber = Integer.parseInt(num); + } + } + if (v != null) { + existingGavs.add(MavenGav.parseGav(g + ":" + a + ":" + v)); + } else { + existingGavs.add(MavenGav.parseGav(g + ":" + a)); + } + } + + // find out which JARs are new + List<MavenGav> updates = new ArrayList<>(); + for (MavenGav gav : gavs) { + MavenGav target; + if (camelVersion != null) { + target = MavenGav.parseGav(gav.getGroupId() + ":" + gav.getArtifactId() + ":" + camelVersion); + } else { + target = MavenGav.parseGav(gav.getGroupId() + ":" + gav.getArtifactId()); + } + updates.add(target); + } + // sort the new JARs being added + updates.sort(mavenGavComparator()); + List<MavenGav> toBeUpdated = new ArrayList<>(); + int changes = 0; + for (MavenGav update : updates) { + if (!existingGavs.contains(update)) { + toBeUpdated.add(update); + changes++; + } + } + + if (changes > 0) { + // respect indent from existing GAVs + String line = IOHelper.loadTextLine(new FileInputStream(file), targetLineNumber); + line = StringHelper.before(line, "<"); + int indent = StringHelper.countChar(line, ' '); + String pad = Strings.repeat(" ", indent); + line = IOHelper.loadTextLine(new FileInputStream(file), targetLineNumber - 1); + line = StringHelper.before(line, "<"); + int indent2 = StringHelper.countChar(line, ' '); + String pad2 = Strings.repeat(" ", indent2); + + // build GAVs to be added to pom.xml + StringJoiner sj = new StringJoiner(""); + for (MavenGav gav : toBeUpdated) { + sj.add(pad).add("<dependency>\n"); + sj.add(pad2).add("<groupId>" + gav.getGroupId() + "</groupId>\n"); + sj.add(pad2).add("<artifactId>" + gav.getArtifactId() + "</artifactId>\n"); + if (gav.getVersion() != null) { + sj.add(pad2).add("<version>" + gav.getVersion() + "</version>\n"); + } + sj.add(pad).add("</dependency>"); + } + + StringJoiner out = new StringJoiner("\n"); + String[] lines = IOHelper.loadText(new FileInputStream(file)).split("\n"); + for (int i = 0; i < lines.length; i++) { + String txt = lines[i]; + out.add(txt); + if (i == targetLineNumber - 1) { + out.add(sj.toString()); + } + } + if (changes > 1) { + outPrinter().println("Updating pom.xml with " + changes + " dependencies added"); + } else { + outPrinter().println("Updating pom.xml with 1 dependency added"); + } + IOHelper.writeText(out.toString(), file); + } else { + outPrinter().println("No updates to pom.xml"); + } } } diff --git a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenGav.java b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenGav.java index 2237c0abeca..dcfb5c515d4 100644 --- a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenGav.java +++ b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenGav.java @@ -16,6 +16,8 @@ */ package org.apache.camel.tooling.maven; +import java.util.Objects; + /** * Maven GAV model with parsing support and special rules for some names: * <ul> @@ -171,6 +173,27 @@ public final class MavenGav { this.classifier = classifier; } + @Override + public boolean equals(Object o) { + if (!(o instanceof MavenGav mavenGav)) { + return false; + } + + return groupId.equals(mavenGav.groupId) && artifactId.equals(mavenGav.artifactId) + && Objects.equals(version, mavenGav.version) && Objects.equals(packaging, mavenGav.packaging) + && Objects.equals(classifier, mavenGav.classifier); + } + + @Override + public int hashCode() { + int result = groupId.hashCode(); + result = 31 * result + artifactId.hashCode(); + result = 31 * result + Objects.hashCode(version); + result = 31 * result + Objects.hashCode(packaging); + result = 31 * result + Objects.hashCode(classifier); + return result; + } + @Override public String toString() { if (version != null) {