From 101cfec9c3b21e40b1731fbbd603402dc7ba1577 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 30 May 2023 09:58:02 +0200 Subject: [PATCH 01/20] JDK Toolchains discovery mojos --- pom.xml | 4 + .../DisplayDiscoveredJdkToolchainsMojo.java | 51 +++ .../jdk/GenerateJdkToolchainsXmlMojo.java | 59 ++++ .../toolchain/jdk/SelectJdkToolchainMojo.java | 167 +++++++++ .../toolchain/jdk/ToolchainDiscoverer.java | 332 ++++++++++++++++++ 5 files changed, 613 insertions(+) create mode 100644 src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java create mode 100644 src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java create mode 100644 src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java create mode 100644 src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java diff --git a/pom.xml b/pom.xml index 7a36744..fd5a670 100644 --- a/pom.xml +++ b/pom.xml @@ -90,6 +90,10 @@ under the License. maven-plugin-annotations provided + + org.codehaus.plexus + plexus-utils + diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java new file mode 100644 index 0000000..33b63e6 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java @@ -0,0 +1,51 @@ +/* + * 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.maven.plugins.toolchain.jdk; + +import java.util.List; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.toolchain.model.PersistedToolchains; +import org.apache.maven.toolchain.model.ToolchainModel; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +@Mojo(name = "display-discovered-jdk-toolchains", requiresProject = false) +public class DisplayDiscoveredJdkToolchainsMojo extends AbstractMojo { + + @Override + public void execute() throws MojoFailureException { + try { + PersistedToolchains toolchains = new ToolchainDiscoverer(getLog()).discoverToolchains(); + List models = toolchains.getToolchains(); + getLog().info("Discovered " + models.size() + " JDK toolchains:"); + for (ToolchainModel model : models) { + getLog().info(" - " + + ((Xpp3Dom) model.getConfiguration()) + .getChild("jdkHome") + .getValue()); + getLog().info(" provides:"); + model.getProvides().forEach((k, v) -> getLog().info(" " + k + ": " + v)); + } + } catch (Exception e) { + throw new MojoFailureException("Unable to retrieve discovered toolchains", e); + } + } +} diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java new file mode 100644 index 0000000..741b965 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java @@ -0,0 +1,59 @@ +/* + * 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.maven.plugins.toolchain.jdk; + +import java.io.StringWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.toolchain.model.PersistedToolchains; +import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Writer; + +@Mojo(name = "generate-jdk-toolchains-xml", requiresProject = false) +public class GenerateJdkToolchainsXmlMojo extends AbstractMojo { + + @Parameter(property = "toolchain.file") + String file; + + @Override + public void execute() throws MojoFailureException { + try { + PersistedToolchains toolchains = new ToolchainDiscoverer(getLog()).discoverToolchains(); + if (file != null) { + Path file = Paths.get(this.file).toAbsolutePath(); + Files.createDirectories(file.getParent()); + try (Writer writer = Files.newBufferedWriter(file)) { + new MavenToolchainsXpp3Writer().write(writer, toolchains); + } + } else { + StringWriter writer = new StringWriter(); + new MavenToolchainsXpp3Writer().write(writer, toolchains); + System.out.println(writer); + } + } catch (Exception e) { + throw new MojoFailureException("Unable to generate toolchains.xml", e); + } + } +} diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java new file mode 100644 index 0000000..f5a3819 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java @@ -0,0 +1,167 @@ +/* + * 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.maven.plugins.toolchain.jdk; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.toolchain.MisconfiguredToolchainException; +import org.apache.maven.toolchain.ToolchainFactory; +import org.apache.maven.toolchain.ToolchainManagerPrivate; +import org.apache.maven.toolchain.ToolchainPrivate; +import org.apache.maven.toolchain.model.PersistedToolchains; +import org.apache.maven.toolchain.model.ToolchainModel; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Check that toolchains requirements are met by currently configured toolchains and + * store the selected toolchains in build context for later retrieval by other plugins. + */ +@Mojo(name = "select-jdk-toolchain", defaultPhase = LifecyclePhase.VALIDATE) +public class SelectJdkToolchainMojo extends AbstractMojo { + + public static final String TOOLCHAIN_TYPE_JDK = "jdk"; + + /** Jdk usage mode */ + public enum JdkMode { + /** always ignore the current JDK */ + Never, + /** to not use a toolchain if the toolchains that would be selected is the current JDK */ + IfSame, + /** favor the current JDK if it matches the requirements */ + IfMatch + } + + /** + */ + @Component + private ToolchainManagerPrivate toolchainManager; + + /** + */ + @Component(hint = TOOLCHAIN_TYPE_JDK) + ToolchainFactory factory; + + /** + * The current build session instance. This is used for toolchain manager API calls. + */ + @Component + private MavenSession session; + + @Parameter(property = "toolchain.jdk.version") + private String version; + + @Parameter(property = "toolchain.jdk.runtime.name") + private String runtimeName; + + @Parameter(property = "toolchain.jdk.runtime.version") + private String runtimeVersion; + + @Parameter(property = "toolchain.jdk.vendor") + private String vendor; + + @Parameter(property = "toolchain.jdk.mode", defaultValue = "IfMatch") + private JdkMode useJdk = JdkMode.IfMatch; + + @Parameter(property = "toolchain.jdk.discover", defaultValue = "true") + private boolean discoverToolchains = true; + + @Override + public void execute() throws MojoFailureException { + try { + doExecute(); + } catch (MisconfiguredToolchainException e) { + throw new MojoFailureException("Unable to select toolchain: " + e, e); + } + } + + private void doExecute() throws MisconfiguredToolchainException, MojoFailureException { + if (version == null && runtimeName == null && runtimeVersion == null && vendor == null) { + return; + } + + ToolchainDiscoverer discoverer = new ToolchainDiscoverer(getLog()); + + Map requirements = new HashMap<>(); + Optional.ofNullable(version).ifPresent(v -> requirements.put("version", v)); + Optional.ofNullable(runtimeName).ifPresent(v -> requirements.put("runtime.name", v)); + Optional.ofNullable(runtimeVersion).ifPresent(v -> requirements.put("runtime.version", v)); + Optional.ofNullable(vendor).ifPresent(v -> requirements.put("vendor", v)); + + ToolchainModel currentJdkToolchainModel = discoverer.getCurrentJdkToolchain(); + ToolchainPrivate currentJdkToolchain = factory.createToolchain(currentJdkToolchainModel); + + if (useJdk == JdkMode.IfMatch + && currentJdkToolchain != null + && currentJdkToolchain.matchesRequirements(requirements)) { + getLog().info("Not using an external toolchain as the current JDK matches the requirements."); + return; + } + + ToolchainPrivate toolchain = Stream.of(toolchainManager.getToolchainsForType(TOOLCHAIN_TYPE_JDK, session)) + .filter(tc -> tc.matchesRequirements(requirements)) + .findFirst() + .orElse(null); + if (toolchain != null) { + getLog().info("Found matching JDK toolchain: " + toolchain); + } else { + getLog().debug("No matching toolchains configured, trying to discover JDK toolchains"); + PersistedToolchains persistedToolchains = discoverer.discoverToolchains(); + getLog().info("Discovered " + persistedToolchains.getToolchains().size() + " JDK toolchains"); + for (ToolchainModel tcm : persistedToolchains.getToolchains()) { + ToolchainPrivate tc = factory.createToolchain(tcm); + if (tc != null && tc.matchesRequirements(requirements)) { + toolchain = tc; + getLog().debug("Discovered matching JDK toolchain: " + toolchain); + break; + } + } + } + if (toolchain == null) { + throw new MojoFailureException( + "Cannot find matching toolchain definitions for the following toolchain types:" + requirements + + System.lineSeparator() + + "Please make sure you define the required toolchains in your ~/.m2/toolchains.xml file."); + } + if (useJdk == JdkMode.IfSame + && currentJdkToolchain != null + && Objects.equals(getJdkHome(currentJdkToolchain), getJdkHome(toolchain))) { + getLog().info("Not using an external toolchain as the current JDK has been selected."); + return; + } + toolchainManager.storeToolchainToBuildContext(toolchain, session); + getLog().info("Found matching JDK toolchain: " + toolchain); + } + + private String getJdkHome(ToolchainPrivate toolchain) { + return ((Xpp3Dom) toolchain.getModel().getConfiguration()) + .getChild("jdkHome") + .getValue(); + } +} diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java new file mode 100644 index 0000000..966a14d --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -0,0 +1,332 @@ +/* + * 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.maven.plugins.toolchain.jdk; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.toolchain.model.PersistedToolchains; +import org.apache.maven.toolchain.model.ToolchainModel; +import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Reader; +import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Writer; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Implementation of ToolchainDiscoverer service + */ +public class ToolchainDiscoverer { + + private static final String DISCOVERED_TOOLCHAINS_CACHE_XML = ".m2/discovered-toolchains-cache.xml"; + + private final Log log; + + private Map cache; + private boolean cacheModified; + + public ToolchainDiscoverer(Log log) { + this.log = log; + } + + /** + * Build the model for the current JDK toolchain + */ + public ToolchainModel getCurrentJdkToolchain() { + Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty("java.home"))); + if (!Files.exists(currentJdkHome.resolve("bin/javac")) + && !Files.exists(currentJdkHome.resolve("bin/javac.exe"))) { + // in case the current JVM is not a JDK + return null; + } + + ToolchainModel model = new ToolchainModel(); + model.setType("jdk"); + Stream.of("java.version", "java.runtime.name", "java.runtime.version", "java.vendor", "java.vendor.version") + .forEach(k -> { + String v = System.getProperty(k); + if (v != null) { + model.addProvide(k.substring(5), v); + } + }); + model.addProvide("current", "true"); + Xpp3Dom config = new Xpp3Dom("configuration"); + Xpp3Dom jdkHome = new Xpp3Dom("jdkHome"); + jdkHome.setValue(currentJdkHome.toString()); + config.addChild(jdkHome); + model.setConfiguration(config); + return model; + } + + /** + * Returns a PersistedToolchains object containing a list of discovered toolchains, + * never null. + */ + public PersistedToolchains discoverToolchains() { + try { + Set jdks = findJdks(); + log.info("Found " + jdks.size() + " possible jdks: " + jdks); + cacheModified = false; + readCache(); + Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty("java.home"))); + List tcs = jdks.parallelStream() + .map(s -> getToolchainModel(currentJdkHome, s)) + .filter(Objects::nonNull) + .sorted(getToolchainModelComparator()) + .collect(Collectors.toList()); + if (this.cacheModified) { + writeCache(); + } + PersistedToolchains ps = new PersistedToolchains(); + ps.setToolchains(tcs); + return ps; + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.warn("Error discovering toolchains: " + e, e); + } else { + log.warn("Error discovering toolchains (enable debug level for more information): " + e); + } + return new PersistedToolchains(); + } + } + + private void readCache() { + try { + cache = new ConcurrentHashMap<>(); + Path cacheFile = getCacheFile(); + if (Files.isRegularFile(cacheFile)) { + try (Reader r = Files.newBufferedReader(cacheFile)) { + PersistedToolchains pt = new MavenToolchainsXpp3Reader().read(r, false); + cache = pt.getToolchains().stream() + .collect(Collectors.toConcurrentMap(this::getJdkHome, Function.identity())); + } + } + } catch (IOException | XmlPullParserException e) { + log.warn("Error reading toolchains cache: " + e); + } + } + + private void writeCache() { + try { + Path cacheFile = getCacheFile(); + Files.createDirectories(cacheFile.getParent()); + try (Writer w = Files.newBufferedWriter(cacheFile)) { + PersistedToolchains pt = new PersistedToolchains(); + List toolchains = new ArrayList<>(); + for (ToolchainModel tc : cache.values()) { + tc = tc.clone(); + tc.getProvides().remove("current"); + toolchains.add(tc); + } + pt.setToolchains(toolchains); + new MavenToolchainsXpp3Writer().write(w, pt); + } + } catch (IOException e) { + log.warn("Error writing toolchains cache: " + e); + } + } + + private static Path getCacheFile() { + return Paths.get(System.getProperty("user.home")).resolve(DISCOVERED_TOOLCHAINS_CACHE_XML); + } + + private Path getJdkHome(ToolchainModel toolchain) { + Xpp3Dom dom = (Xpp3Dom) toolchain.getConfiguration(); + Xpp3Dom javahome = dom != null ? dom.getChild("jdkHome") : null; + String jdk = javahome != null ? javahome.getValue() : null; + return Paths.get(Objects.requireNonNull(jdk)); + } + + ToolchainModel getToolchainModel(Path currentJdkHome, Path jdk) { + log.debug("Computing model for " + jdk); + + ToolchainModel model = cache.get(jdk); + if (model == null) { + model = doGetToolchainModel(jdk); + cache.put(jdk, model); + cacheModified = true; + } + + if (Objects.equals(jdk, currentJdkHome)) { + model.getProvides().setProperty("current", "true"); + } + return model; + } + + ToolchainModel doGetToolchainModel(Path jdk) { + Path bin = jdk.resolve("bin"); + Path java = bin.resolve("java"); + if (!java.toFile().canExecute()) { + java = bin.resolve("java.exe"); + if (!java.toFile().canExecute()) { + log.debug("JDK toolchain discovered at " + jdk + " will be ignored: unable to find java executable"); + return null; + } + } + List lines; + try { + Path temp = Files.createTempFile("jdk-opts-", ".out"); + try { + new ProcessBuilder() + .command(java.toString(), "-XshowSettings:properties", "-version") + .redirectError(temp.toFile()) + .start() + .waitFor(); + lines = Files.readAllLines(temp); + } finally { + Files.delete(temp); + } + } catch (IOException | InterruptedException e) { + log.debug("JDK toolchain discovered at " + jdk + " will be ignored: unable to execute java: " + e); + return null; + } + + Map properties = new LinkedHashMap<>(); + for (String name : Arrays.asList( + "java.version", "java.runtime.name", "java.runtime.version", "java.vendor", "java.vendor.version")) { + String v = lines.stream() + .filter(l -> l.contains(name)) + .map(l -> l.replaceFirst(".*=\\s*(.*)", "$1")) + .findFirst() + .orElse(null); + String k = name.substring(5); + if (v != null) { + properties.put(k, v); + } + } + if (!properties.containsKey("version")) { + log.debug("JDK toolchain discovered at " + jdk + " will be ignored: could not obtain java.version"); + return null; + } + + ToolchainModel model = new ToolchainModel(); + model.setType("jdk"); + properties.forEach(model::addProvide); + Xpp3Dom configuration = new Xpp3Dom("configuration"); + Xpp3Dom jdkHome = new Xpp3Dom("jdkHome"); + jdkHome.setValue(jdk.toString()); + configuration.addChild(jdkHome); + model.setConfiguration(configuration); + return model; + } + + private static Path getCanonicalPath(Path path) { + try { + return path.toRealPath(); + } catch (IOException e) { + return getCanonicalPath(path.getParent()).resolve(path.getFileName()); + } + } + + Comparator getToolchainModelComparator() { + return Comparator.comparing( + (ToolchainModel tc) -> tc.getProvides().getProperty("version"), this::compareVersion) + .reversed() + .thenComparing(tc -> tc.getProvides().getProperty("vendor")); + } + + int compareVersion(String v1, String v2) { + String[] s1 = v1.split("\\."); + String[] s2 = v2.split("\\."); + return compare(s1, s2); + } + + static > int compare(T[] a, T[] b) { + int length = Math.min(a.length, b.length); + for (int i = 0; i < length; i++) { + T oa = a[i]; + T ob = b[i]; + if (oa != ob) { + // A null element is less than a non-null element + if (oa == null || ob == null) { + return oa == null ? -1 : 1; + } + int v = oa.compareTo(ob); + if (v != 0) { + return v; + } + } + } + return a.length - b.length; + } + + private Set findJdks() { + Set jdks = new HashSet<>(); + final String userHome = System.getProperty("user.home"); + List installedDirs = new ArrayList<>(); + // jdk installed by third + installedDirs.add(Paths.get(userHome, ".jdks")); + installedDirs.add(Paths.get(userHome, ".m2/jdks")); + installedDirs.add(Paths.get(userHome, ".sdkman/candidates/java")); + installedDirs.add(Paths.get(userHome, ".gradle/jdks")); + installedDirs.add(Paths.get(userHome, ".jenv/versions")); + installedDirs.add(Paths.get(userHome, ".jbang/cache/jdks")); + installedDirs.add(Paths.get(userHome, ".asdf/installs")); + installedDirs.add(Paths.get(userHome, ".jabba/jdk")); + // os related directories + String osname = System.getProperty("os.name").toLowerCase(Locale.ROOT); + if (osname.startsWith("mac")) { + installedDirs.add(Paths.get("/Library/Java/JavaVirtualMachines")); + installedDirs.add(Paths.get(userHome, "Library/Java/JavaVirtualMachines")); + } else if (osname.startsWith("win")) { + installedDirs.add(Paths.get("C:\\Program Files\\Java\\")); + } else { + installedDirs.add(Paths.get("/usr/jdk")); + installedDirs.add(Paths.get("/usr/java")); + installedDirs.add(Paths.get("/opt/java")); + installedDirs.add(Paths.get("/usr/lib/jvm")); + } + for (Path dest : installedDirs) { + if (Files.isDirectory(dest)) { + try { + List subdirs = Files.list(dest).collect(Collectors.toList()); + for (Path subdir : subdirs) { + if (Files.exists(subdir.resolve("bin/javac")) + || Files.exists(subdir.resolve("bin/javac.exe"))) { + jdks.add(getCanonicalPath(subdir)); + } else if (Files.exists(subdir.resolve("Contents/Home/bin/javac"))) { + jdks.add(getCanonicalPath(subdir.resolve("Contents/Home"))); + } + } + } catch (IOException e) { + // ignore + } + } + } + return jdks; + } +} From abac003eb5b2005c89a82de5c148846f3b287895 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 27 Feb 2024 18:43:27 +0100 Subject: [PATCH 02/20] Fixes --- .../DisplayDiscoveredJdkToolchainsMojo.java | 3 ++ .../jdk/GenerateJdkToolchainsXmlMojo.java | 3 ++ .../toolchain/jdk/ToolchainDiscoverer.java | 53 ++++++++++--------- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java index 33b63e6..3a9643a 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java @@ -27,6 +27,9 @@ import org.apache.maven.toolchain.model.ToolchainModel; import org.codehaus.plexus.util.xml.Xpp3Dom; +/** + * Run the JDK discoverer and display a summary of found toolchains. + */ @Mojo(name = "display-discovered-jdk-toolchains", requiresProject = false) public class DisplayDiscoveredJdkToolchainsMojo extends AbstractMojo { diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java index 741b965..7fdefc0 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java @@ -31,6 +31,9 @@ import org.apache.maven.toolchain.model.PersistedToolchains; import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Writer; +/** + * Run the JDK discovery mechanism and generates the toolchains XML. + */ @Mojo(name = "generate-jdk-toolchains-xml", requiresProject = false) public class GenerateJdkToolchainsXmlMojo extends AbstractMojo { diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 966a14d..370cd0d 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -27,7 +27,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -48,7 +47,7 @@ import org.codehaus.plexus.util.xml.pull.XmlPullParserException; /** - * Implementation of ToolchainDiscoverer service + * Toolchain discoverer service */ public class ToolchainDiscoverer { @@ -216,17 +215,12 @@ ToolchainModel doGetToolchainModel(Path jdk) { } Map properties = new LinkedHashMap<>(); - for (String name : Arrays.asList( - "java.version", "java.runtime.name", "java.runtime.version", "java.vendor", "java.vendor.version")) { - String v = lines.stream() - .filter(l -> l.contains(name)) + for (String name : Arrays.asList("version", "runtime.name", "runtime.version", "vendor", "vendor.version")) { + lines.stream() + .filter(l -> l.contains("java." + name)) .map(l -> l.replaceFirst(".*=\\s*(.*)", "$1")) .findFirst() - .orElse(null); - String k = name.substring(5); - if (v != null) { - properties.put(k, v); - } + .ifPresent(value -> properties.put(name, value)); } if (!properties.containsKey("version")) { log.debug("JDK toolchain discovered at " + jdk + " will be ignored: could not obtain java.version"); @@ -285,7 +279,11 @@ static > int compare(T[] a, T[] b) { } private Set findJdks() { - Set jdks = new HashSet<>(); + // check environment variables for JAVA{xx}_HOME + List dirsToTest = System.getenv().entrySet().stream() + .filter(e -> e.getKey().startsWith("JAVA") && e.getKey().endsWith("_HOME")) + .map(e -> Paths.get(e.getValue())) + .collect(Collectors.toList()); final String userHome = System.getProperty("user.home"); List installedDirs = new ArrayList<>(); // jdk installed by third @@ -299,10 +297,12 @@ private Set findJdks() { installedDirs.add(Paths.get(userHome, ".jabba/jdk")); // os related directories String osname = System.getProperty("os.name").toLowerCase(Locale.ROOT); - if (osname.startsWith("mac")) { + boolean macos = osname.startsWith("mac"); + boolean win = osname.startsWith("win"); + if (macos) { installedDirs.add(Paths.get("/Library/Java/JavaVirtualMachines")); installedDirs.add(Paths.get(userHome, "Library/Java/JavaVirtualMachines")); - } else if (osname.startsWith("win")) { + } else if (win) { installedDirs.add(Paths.get("C:\\Program Files\\Java\\")); } else { installedDirs.add(Paths.get("/usr/jdk")); @@ -312,21 +312,24 @@ private Set findJdks() { } for (Path dest : installedDirs) { if (Files.isDirectory(dest)) { - try { - List subdirs = Files.list(dest).collect(Collectors.toList()); - for (Path subdir : subdirs) { - if (Files.exists(subdir.resolve("bin/javac")) - || Files.exists(subdir.resolve("bin/javac.exe"))) { - jdks.add(getCanonicalPath(subdir)); - } else if (Files.exists(subdir.resolve("Contents/Home/bin/javac"))) { - jdks.add(getCanonicalPath(subdir.resolve("Contents/Home"))); - } - } + try (Stream stream = Files.list(dest)) { + stream.forEach(dir -> { + dirsToTest.add(dir); + dirsToTest.add(dir.resolve("Contents/Home")); + }); } catch (IOException e) { // ignore } } } - return jdks; + // only keep directories that have a javac file + return dirsToTest.stream() + .filter(ToolchainDiscoverer::hasJavaC) + .map(ToolchainDiscoverer::getCanonicalPath) + .collect(Collectors.toSet()); + } + + private static boolean hasJavaC(Path subdir) { + return Files.exists(subdir.resolve("bin/javac")) || Files.exists(subdir.resolve("bin/javac.exe")); } } From f228ca01768c25e2dd708b5a6bf909273cc56963 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 11:00:39 +0100 Subject: [PATCH 03/20] Allow sorting JDKs + fixes following review --- pom.xml | 22 ++ .../DisplayDiscoveredJdkToolchainsMojo.java | 25 +- .../jdk/GenerateJdkToolchainsXmlMojo.java | 14 +- .../toolchain/jdk/SelectJdkToolchainMojo.java | 110 ++++++-- .../toolchain/jdk/ToolchainDiscoverer.java | 262 ++++++++++++------ .../jdk/ToolchainDiscovererTest.java | 39 +++ 6 files changed, 363 insertions(+), 109 deletions(-) create mode 100644 src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java diff --git a/pom.xml b/pom.xml index fd5a670..97b0aa5 100644 --- a/pom.xml +++ b/pom.xml @@ -93,11 +93,33 @@ under the License. org.codehaus.plexus plexus-utils + 4.0.0 + + + org.codehaus.plexus + plexus-xml + 3.0.0 + + + org.slf4j + slf4j-api + 1.7.36 + + + + org.junit.jupiter + junit-jupiter-api + 5.10.2 + test + + org.eclipse.sisu + sisu-maven-plugin + com.diffplug.spotless spotless-maven-plugin diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java index 3a9643a..3e80946 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java @@ -18,11 +18,14 @@ */ package org.apache.maven.plugins.toolchain.jdk; +import javax.inject.Inject; + import java.util.List; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.toolchain.model.PersistedToolchains; import org.apache.maven.toolchain.model.ToolchainModel; import org.codehaus.plexus.util.xml.Xpp3Dom; @@ -33,10 +36,30 @@ @Mojo(name = "display-discovered-jdk-toolchains", requiresProject = false) public class DisplayDiscoveredJdkToolchainsMojo extends AbstractMojo { + /** + * Comparator used to sort JDK toolchains for selection. + * This property is a comma separated list of values which may contains: + *
    + *
  • {@code lts}: prefer JDK with LTS version
  • + *
  • {@code current}: prefer the current JDK
  • + *
  • {@code env}: prefer JDKs defined using {@code JAVA\{xx\}_HOME} environment variables
  • + *
  • {@code version}: prefer JDK with higher versions
  • + *
  • {@code vendor}: order JDK by vendor name (usually as a last comparator to ensure a stable order)
  • + *
+ */ + @Parameter(property = "toolchain.jdk.comparator", defaultValue = "lts,current,env,version,vendor") + String comparator; + + /** + * Toolchain discoverer + */ + @Inject + ToolchainDiscoverer discoverer; + @Override public void execute() throws MojoFailureException { try { - PersistedToolchains toolchains = new ToolchainDiscoverer(getLog()).discoverToolchains(); + PersistedToolchains toolchains = discoverer.discoverToolchains(comparator); List models = toolchains.getToolchains(); getLog().info("Discovered " + models.size() + " JDK toolchains:"); for (ToolchainModel model : models) { diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java index 7fdefc0..1c2c0d0 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java @@ -18,6 +18,8 @@ */ package org.apache.maven.plugins.toolchain.jdk; +import javax.inject.Inject; + import java.io.StringWriter; import java.io.Writer; import java.nio.file.Files; @@ -37,13 +39,23 @@ @Mojo(name = "generate-jdk-toolchains-xml", requiresProject = false) public class GenerateJdkToolchainsXmlMojo extends AbstractMojo { + /** + * The path and name pf the toolchain XML file that will be generated. + * If not provided, the XML will be written to the standard output. + */ @Parameter(property = "toolchain.file") String file; + /** + * Toolchain discoverer + */ + @Inject + ToolchainDiscoverer discoverer; + @Override public void execute() throws MojoFailureException { try { - PersistedToolchains toolchains = new ToolchainDiscoverer(getLog()).discoverToolchains(); + PersistedToolchains toolchains = discoverer.discoverToolchains(); if (file != null) { Path file = Paths.get(this.file).toAbsolutePath(); Files.createDirectories(file.getParent()); diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java index f5a3819..dc94f89 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java @@ -18,6 +18,9 @@ */ package org.apache.maven.plugins.toolchain.jdk; +import javax.inject.Inject; +import javax.inject.Named; + import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -27,7 +30,6 @@ import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -39,6 +41,11 @@ import org.apache.maven.toolchain.model.ToolchainModel; import org.codehaus.plexus.util.xml.Xpp3Dom; +import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.RUNTIME_NAME; +import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.RUNTIME_VERSION; +import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.VENDOR; +import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.VERSION; + /** * Check that toolchains requirements are met by currently configured toolchains and * store the selected toolchains in build context for later retrieval by other plugins. @@ -59,39 +66,86 @@ public enum JdkMode { } /** + * The version constraint for the JDK toolchain to select. */ - @Component - private ToolchainManagerPrivate toolchainManager; - - /** - */ - @Component(hint = TOOLCHAIN_TYPE_JDK) - ToolchainFactory factory; - - /** - * The current build session instance. This is used for toolchain manager API calls. - */ - @Component - private MavenSession session; - @Parameter(property = "toolchain.jdk.version") private String version; + /** + * The runtime name constraint for the JDK toolchain to select. + */ @Parameter(property = "toolchain.jdk.runtime.name") private String runtimeName; + /** + * The runtime version constraint for the JDK toolchain to select. + */ @Parameter(property = "toolchain.jdk.runtime.version") private String runtimeVersion; + /** + * The vendor constraint for the JDK toolchain to select. + */ @Parameter(property = "toolchain.jdk.vendor") private String vendor; + /** + * The matching mode, either {@code IfMatch} (the default), {@code IfSame}, or {@code Never}. + * If {@code IfMatch} is used, a toolchain will not be selected if the running JDK does + * match the provided constraints. This is the default and provides better performances as it + * avoids forking a different process when it's not required. The {@code IfSame} avoids + * selecting a toolchain if the toolchain selected is exactly the same as the running JDK. + * THe {@code Never} option will always select the toolchain. + */ @Parameter(property = "toolchain.jdk.mode", defaultValue = "IfMatch") private JdkMode useJdk = JdkMode.IfMatch; + /** + * Automatically discover JDK toolchains using the built-in heuristic. + * The default value is {@code true}. + */ @Parameter(property = "toolchain.jdk.discover", defaultValue = "true") private boolean discoverToolchains = true; + /** + * Comparator used to sort JDK toolchains for selection. + * This property is a comma separated list of values which may contains: + *
    + *
  • {@code lts}: prefer JDK with LTS version
  • + *
  • {@code current}: prefer the current JDK
  • + *
  • {@code env}: prefer JDKs defined using {@code JAVA\{xx\}_HOME} environment variables
  • + *
  • {@code version}: prefer JDK with higher versions
  • + *
  • {@code vendor}: order JDK by vendor name (usually as a last comparator to ensure a stable order)
  • + *
+ */ + @Parameter(property = "toolchain.jdk.comparator", defaultValue = "lts,current,env,version,vendor") + private String comparator; + + /** + * Toolchain manager + */ + @Inject + private ToolchainManagerPrivate toolchainManager; + + /** + * Toolchain factory + */ + @Inject + @Named(TOOLCHAIN_TYPE_JDK) + ToolchainFactory factory; + + /** + * The current build session instance. This is used for toolchain manager API calls. + */ + @Inject + private MavenSession session; + + /** + * Toolchain discoverer + */ + @Inject + ToolchainDiscoverer discoverer; + @Override public void execute() throws MojoFailureException { try { @@ -106,16 +160,16 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce return; } - ToolchainDiscoverer discoverer = new ToolchainDiscoverer(getLog()); - Map requirements = new HashMap<>(); - Optional.ofNullable(version).ifPresent(v -> requirements.put("version", v)); - Optional.ofNullable(runtimeName).ifPresent(v -> requirements.put("runtime.name", v)); - Optional.ofNullable(runtimeVersion).ifPresent(v -> requirements.put("runtime.version", v)); - Optional.ofNullable(vendor).ifPresent(v -> requirements.put("vendor", v)); + Optional.ofNullable(version).ifPresent(v -> requirements.put(VERSION, v)); + Optional.ofNullable(runtimeName).ifPresent(v -> requirements.put(RUNTIME_NAME, v)); + Optional.ofNullable(runtimeVersion).ifPresent(v -> requirements.put(RUNTIME_VERSION, v)); + Optional.ofNullable(vendor).ifPresent(v -> requirements.put(VENDOR, v)); - ToolchainModel currentJdkToolchainModel = discoverer.getCurrentJdkToolchain(); - ToolchainPrivate currentJdkToolchain = factory.createToolchain(currentJdkToolchainModel); + ToolchainModel currentJdkToolchainModel = + discoverer.getCurrentJdkToolchain().orElse(null); + ToolchainPrivate currentJdkToolchain = + currentJdkToolchainModel != null ? factory.createToolchain(currentJdkToolchainModel) : null; if (useJdk == JdkMode.IfMatch && currentJdkToolchain != null @@ -130,10 +184,13 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce .orElse(null); if (toolchain != null) { getLog().info("Found matching JDK toolchain: " + toolchain); - } else { + } + + if (toolchain == null && discoverToolchains) { getLog().debug("No matching toolchains configured, trying to discover JDK toolchains"); - PersistedToolchains persistedToolchains = discoverer.discoverToolchains(); + PersistedToolchains persistedToolchains = discoverer.discoverToolchains(comparator); getLog().info("Discovered " + persistedToolchains.getToolchains().size() + " JDK toolchains"); + for (ToolchainModel tcm : persistedToolchains.getToolchains()) { ToolchainPrivate tc = factory.createToolchain(tcm); if (tc != null && tc.matchesRequirements(requirements)) { @@ -143,18 +200,21 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce } } } + if (toolchain == null) { throw new MojoFailureException( "Cannot find matching toolchain definitions for the following toolchain types:" + requirements + System.lineSeparator() + "Please make sure you define the required toolchains in your ~/.m2/toolchains.xml file."); } + if (useJdk == JdkMode.IfSame && currentJdkToolchain != null && Objects.equals(getJdkHome(currentJdkToolchain), getJdkHome(toolchain))) { getLog().info("Not using an external toolchain as the current JDK has been selected."); return; } + toolchainManager.storeToolchainToBuildContext(toolchain, session); getLog().info("Found matching JDK toolchain: " + toolchain); } diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 370cd0d..b840bbb 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -18,6 +18,9 @@ */ package org.apache.maven.plugins.toolchain.jdk; +import javax.inject.Named; +import javax.inject.Singleton; + import java.io.IOException; import java.io.Reader; import java.io.Writer; @@ -25,87 +28,129 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.maven.plugin.logging.Log; import org.apache.maven.toolchain.model.PersistedToolchains; import org.apache.maven.toolchain.model.ToolchainModel; import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Reader; import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Writer; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.maven.plugins.toolchain.jdk.SelectJdkToolchainMojo.TOOLCHAIN_TYPE_JDK; /** * Toolchain discoverer service */ +@Named +@Singleton public class ToolchainDiscoverer { - private static final String DISCOVERED_TOOLCHAINS_CACHE_XML = ".m2/discovered-toolchains-cache.xml"; + public static final String JAVA = "java."; + public static final String VERSION = "version"; + public static final String RUNTIME_NAME = "runtime.name"; + public static final String RUNTIME_VERSION = "runtime.version"; + public static final String VENDOR = "vendor"; + public static final String VENDOR_VERSION = "vendor.version"; + public static final String[] PROPERTIES = {VERSION, RUNTIME_NAME, RUNTIME_VERSION, VENDOR, VENDOR_VERSION}; + + public static final String DISCOVERED_TOOLCHAINS_CACHE_XML = ".m2/discovered-toolchains-cache.xml"; + + public static final String CURRENT = "current"; + public static final String ENV = "env"; + public static final String LTS = "lts"; + + public static final String JDK_HOME = "jdkHome"; + public static final String JAVA_HOME = "java.home"; - private final Log log; + private static final String COMMA = ","; + public static final String USER_HOME = "user.home"; + + private final Logger log = LoggerFactory.getLogger(getClass()); private Map cache; private boolean cacheModified; - public ToolchainDiscoverer(Log log) { - this.log = log; - } - /** * Build the model for the current JDK toolchain */ - public ToolchainModel getCurrentJdkToolchain() { - Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty("java.home"))); - if (!Files.exists(currentJdkHome.resolve("bin/javac")) - && !Files.exists(currentJdkHome.resolve("bin/javac.exe"))) { + public Optional getCurrentJdkToolchain() { + Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty(JAVA_HOME))); + if (hasJavaC(currentJdkHome)) { // in case the current JVM is not a JDK - return null; + return Optional.empty(); } - ToolchainModel model = new ToolchainModel(); - model.setType("jdk"); - Stream.of("java.version", "java.runtime.name", "java.runtime.version", "java.vendor", "java.vendor.version") - .forEach(k -> { - String v = System.getProperty(k); - if (v != null) { - model.addProvide(k.substring(5), v); - } - }); - model.addProvide("current", "true"); + model.setType(TOOLCHAIN_TYPE_JDK); + Stream.of(PROPERTIES).forEach(k -> { + String v = System.getProperty(JAVA + k); + if (v != null) { + model.addProvide(k.substring(JAVA.length()), v); + } + }); + model.addProvide(CURRENT, "true"); Xpp3Dom config = new Xpp3Dom("configuration"); - Xpp3Dom jdkHome = new Xpp3Dom("jdkHome"); + Xpp3Dom jdkHome = new Xpp3Dom(JDK_HOME); jdkHome.setValue(currentJdkHome.toString()); config.addChild(jdkHome); model.setConfiguration(config); - return model; + return Optional.of(model); + } + + public PersistedToolchains discoverToolchains() { + return discoverToolchains(LTS + COMMA + VERSION + COMMA + VENDOR); } /** * Returns a PersistedToolchains object containing a list of discovered toolchains, * never null. */ - public PersistedToolchains discoverToolchains() { + public PersistedToolchains discoverToolchains(String comparator) { try { Set jdks = findJdks(); log.info("Found " + jdks.size() + " possible jdks: " + jdks); cacheModified = false; readCache(); - Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty("java.home"))); + Map> flags = new HashMap<>(); + Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty(JAVA_HOME))); + flags.computeIfAbsent(currentJdkHome, p -> new HashSet<>()).add(CURRENT); + // check environment variables for JAVA{xx}_HOME + System.getenv().entrySet().stream() + .filter(e -> e.getKey().startsWith("JAVA") && e.getKey().endsWith("_HOME")) + .map(e -> Paths.get(e.getValue())) + .map(ToolchainDiscoverer::getCanonicalPath) + .forEach(path -> + flags.computeIfAbsent(path, p -> new HashSet<>()).add(ENV)); + List tcs = jdks.parallelStream() - .map(s -> getToolchainModel(currentJdkHome, s)) - .filter(Objects::nonNull) - .sorted(getToolchainModelComparator()) + .map(s -> { + ToolchainModel tc = getToolchainModel(s); + for (String flag : flags.getOrDefault(s, Collections.emptySet())) { + tc.getProvides().setProperty(flag, "true"); + } + String version = tc.getProvides().getProperty(VERSION); + if (isLts(version)) { + tc.getProvides().setProperty(LTS, "true"); + } + return tc; + }) + .sorted(getToolchainModelComparator(comparator)) .collect(Collectors.toList()); if (this.cacheModified) { writeCache(); @@ -123,6 +168,14 @@ public PersistedToolchains discoverToolchains() { } } + private static boolean isLts(String version) { + return version.startsWith("1.8.") + || version.startsWith("11.") + || version.startsWith("17.") + || version.startsWith("21.") + || version.startsWith("25."); + } + private void readCache() { try { cache = new ConcurrentHashMap<>(); @@ -131,6 +184,16 @@ private void readCache() { try (Reader r = Files.newBufferedReader(cacheFile)) { PersistedToolchains pt = new MavenToolchainsXpp3Reader().read(r, false); cache = pt.getToolchains().stream() + // Remove stale entries + .filter(tc -> { + // If the bin/java executable is not available anymore, remove this TC + if (!hasJavaC(getJdkHome(tc))) { + cacheModified = false; + return false; + } else { + return true; + } + }) .collect(Collectors.toConcurrentMap(this::getJdkHome, Function.identity())); } } @@ -145,13 +208,15 @@ private void writeCache() { Files.createDirectories(cacheFile.getParent()); try (Writer w = Files.newBufferedWriter(cacheFile)) { PersistedToolchains pt = new PersistedToolchains(); - List toolchains = new ArrayList<>(); - for (ToolchainModel tc : cache.values()) { - tc = tc.clone(); - tc.getProvides().remove("current"); - toolchains.add(tc); - } - pt.setToolchains(toolchains); + pt.setToolchains(cache.values().stream() + .map(tc -> { + ToolchainModel model = tc.clone(); + model.getProvides().remove(CURRENT); + model.getProvides().remove(ENV); + return model; + }) + .sorted(version().thenComparing(vendor())) + .collect(Collectors.toList())); new MavenToolchainsXpp3Writer().write(w, pt); } } catch (IOException e) { @@ -160,17 +225,17 @@ private void writeCache() { } private static Path getCacheFile() { - return Paths.get(System.getProperty("user.home")).resolve(DISCOVERED_TOOLCHAINS_CACHE_XML); + return Paths.get(System.getProperty(USER_HOME)).resolve(DISCOVERED_TOOLCHAINS_CACHE_XML); } private Path getJdkHome(ToolchainModel toolchain) { Xpp3Dom dom = (Xpp3Dom) toolchain.getConfiguration(); - Xpp3Dom javahome = dom != null ? dom.getChild("jdkHome") : null; + Xpp3Dom javahome = dom != null ? dom.getChild(JDK_HOME) : null; String jdk = javahome != null ? javahome.getValue() : null; return Paths.get(Objects.requireNonNull(jdk)); } - ToolchainModel getToolchainModel(Path currentJdkHome, Path jdk) { + ToolchainModel getToolchainModel(Path jdk) { log.debug("Computing model for " + jdk); ToolchainModel model = cache.get(jdk); @@ -180,17 +245,13 @@ ToolchainModel getToolchainModel(Path currentJdkHome, Path jdk) { cacheModified = true; } - if (Objects.equals(jdk, currentJdkHome)) { - model.getProvides().setProperty("current", "true"); - } return model; } ToolchainModel doGetToolchainModel(Path jdk) { - Path bin = jdk.resolve("bin"); - Path java = bin.resolve("java"); + Path java = jdk.resolve("bin/java"); if (!java.toFile().canExecute()) { - java = bin.resolve("java.exe"); + java = jdk.resolve("bin/java.exe"); if (!java.toFile().canExecute()) { log.debug("JDK toolchain discovered at " + jdk + " will be ignored: unable to find java executable"); return null; @@ -215,23 +276,23 @@ ToolchainModel doGetToolchainModel(Path jdk) { } Map properties = new LinkedHashMap<>(); - for (String name : Arrays.asList("version", "runtime.name", "runtime.version", "vendor", "vendor.version")) { + Stream.of(PROPERTIES).forEach(name -> { lines.stream() - .filter(l -> l.contains("java." + name)) + .filter(l -> l.contains(JAVA + name)) .map(l -> l.replaceFirst(".*=\\s*(.*)", "$1")) .findFirst() .ifPresent(value -> properties.put(name, value)); - } - if (!properties.containsKey("version")) { - log.debug("JDK toolchain discovered at " + jdk + " will be ignored: could not obtain java.version"); + }); + if (!properties.containsKey(VERSION)) { + log.debug("JDK toolchain discovered at " + jdk + " will be ignored: could not obtain " + JAVA + VERSION); return null; } ToolchainModel model = new ToolchainModel(); - model.setType("jdk"); + model.setType(TOOLCHAIN_TYPE_JDK); properties.forEach(model::addProvide); Xpp3Dom configuration = new Xpp3Dom("configuration"); - Xpp3Dom jdkHome = new Xpp3Dom("jdkHome"); + Xpp3Dom jdkHome = new Xpp3Dom(JDK_HOME); jdkHome.setValue(jdk.toString()); configuration.addChild(jdkHome); model.setConfiguration(configuration); @@ -246,45 +307,82 @@ private static Path getCanonicalPath(Path path) { } } - Comparator getToolchainModelComparator() { - return Comparator.comparing( - (ToolchainModel tc) -> tc.getProvides().getProperty("version"), this::compareVersion) - .reversed() - .thenComparing(tc -> tc.getProvides().getProperty("vendor")); + Comparator getToolchainModelComparator(String comparator) { + Comparator c = null; + for (String part : comparator.split(COMMA)) { + c = c == null ? getComparator(part) : c.thenComparing(getComparator(part)); + } + return c; } - int compareVersion(String v1, String v2) { - String[] s1 = v1.split("\\."); - String[] s2 = v2.split("\\."); - return compare(s1, s2); + private Comparator getComparator(String part) { + switch (part.trim().toLowerCase(Locale.ROOT)) { + case LTS: + return lts(); + case VENDOR: + return vendor(); + case ENV: + return env(); + case CURRENT: + return current(); + case VERSION: + return version(); + default: + throw new IllegalArgumentException("Unsupported comparator: " + part + + ". Supported comparators are: vendor, env, current, lts and version."); + } } - static > int compare(T[] a, T[] b) { - int length = Math.min(a.length, b.length); - for (int i = 0; i < length; i++) { - T oa = a[i]; - T ob = b[i]; - if (oa != ob) { - // A null element is less than a non-null element - if (oa == null || ob == null) { - return oa == null ? -1 : 1; - } - int v = oa.compareTo(ob); - if (v != 0) { - return v; - } - } - } - return a.length - b.length; + Comparator lts() { + return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().containsKey(LTS) ? -1 : +1); + } + + Comparator vendor() { + return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().getProperty(VENDOR)); + } + + Comparator env() { + return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().containsKey(ENV) ? -1 : +1); + } + + Comparator current() { + return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().containsKey(CURRENT) ? -1 : +1); + } + + Comparator version() { + return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().getProperty(VERSION), (v1, v2) -> { + String[] a = v1.split("\\."); + String[] b = v2.split("\\."); + int length = Math.min(a.length, b.length); + for (int i = 0; i < length; i++) { + String oa = a[i]; + String ob = b[i]; + if (!Objects.equals(oa, ob)) { + // A null element is less than a non-null element + if (oa == null || ob == null) { + return oa == null ? -1 : 1; + } + int v = oa.compareTo(ob); + if (v != 0) { + return v; + } + } + } + return a.length - b.length; + }) + .reversed(); } private Set findJdks() { + List dirsToTest = new ArrayList<>(); + // add current JDK + dirsToTest.add(Paths.get(System.getProperty(JAVA_HOME))); // check environment variables for JAVA{xx}_HOME - List dirsToTest = System.getenv().entrySet().stream() + System.getenv().entrySet().stream() .filter(e -> e.getKey().startsWith("JAVA") && e.getKey().endsWith("_HOME")) .map(e -> Paths.get(e.getValue())) - .collect(Collectors.toList()); - final String userHome = System.getProperty("user.home"); + .forEach(dirsToTest::add); + final String userHome = System.getProperty(USER_HOME); List installedDirs = new ArrayList<>(); // jdk installed by third installedDirs.add(Paths.get(userHome, ".jdks")); diff --git a/src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java b/src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java new file mode 100644 index 0000000..f8c756d --- /dev/null +++ b/src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java @@ -0,0 +1,39 @@ +/* + * 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.maven.plugins.toolchain.jdk; + +import org.apache.maven.toolchain.model.PersistedToolchains; +import org.junit.jupiter.api.Test; + +import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.CURRENT; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ToolchainDiscovererTest { + + @Test + void testDiscovery() { + ToolchainDiscoverer discoverer = new ToolchainDiscoverer(); + PersistedToolchains persistedToolchains = discoverer.discoverToolchains(); + assertNotNull(persistedToolchains); + + assertTrue(persistedToolchains.getToolchains().stream() + .anyMatch(tc -> tc.getProvides().containsKey(CURRENT))); + } +} From 99dd2b9ffedf670bf9ae75c42293b3220ef46db6 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 11:03:03 +0100 Subject: [PATCH 04/20] Fix exception handling in mojos --- .../DisplayDiscoveredJdkToolchainsMojo.java | 25 +++++++------------ .../jdk/GenerateJdkToolchainsXmlMojo.java | 3 ++- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java index 3e80946..9dbdf22 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java @@ -23,7 +23,6 @@ import java.util.List; import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.toolchain.model.PersistedToolchains; @@ -57,21 +56,15 @@ public class DisplayDiscoveredJdkToolchainsMojo extends AbstractMojo { ToolchainDiscoverer discoverer; @Override - public void execute() throws MojoFailureException { - try { - PersistedToolchains toolchains = discoverer.discoverToolchains(comparator); - List models = toolchains.getToolchains(); - getLog().info("Discovered " + models.size() + " JDK toolchains:"); - for (ToolchainModel model : models) { - getLog().info(" - " - + ((Xpp3Dom) model.getConfiguration()) - .getChild("jdkHome") - .getValue()); - getLog().info(" provides:"); - model.getProvides().forEach((k, v) -> getLog().info(" " + k + ": " + v)); - } - } catch (Exception e) { - throw new MojoFailureException("Unable to retrieve discovered toolchains", e); + public void execute() { + PersistedToolchains toolchains = discoverer.discoverToolchains(comparator); + List models = toolchains.getToolchains(); + getLog().info("Discovered " + models.size() + " JDK toolchains:"); + for (ToolchainModel model : models) { + getLog().info(" - " + + ((Xpp3Dom) model.getConfiguration()).getChild("jdkHome").getValue()); + getLog().info(" provides:"); + model.getProvides().forEach((k, v) -> getLog().info(" " + k + ": " + v)); } } } diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java index 1c2c0d0..2becdf2 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java @@ -20,6 +20,7 @@ import javax.inject.Inject; +import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.nio.file.Files; @@ -67,7 +68,7 @@ public void execute() throws MojoFailureException { new MavenToolchainsXpp3Writer().write(writer, toolchains); System.out.println(writer); } - } catch (Exception e) { + } catch (IOException e) { throw new MojoFailureException("Unable to generate toolchains.xml", e); } } From 1cd938cfa39b262e669378135b7df89559408300 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 12:49:59 +0100 Subject: [PATCH 05/20] Support selecting toolchain based on environment variable --- .../toolchain/jdk/SelectJdkToolchainMojo.java | 51 ++++++++++++++++--- .../toolchain/jdk/ToolchainDiscoverer.java | 20 ++++---- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java index dc94f89..85da9f1 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java @@ -34,6 +34,7 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.toolchain.MisconfiguredToolchainException; +import org.apache.maven.toolchain.RequirementMatcherFactory; import org.apache.maven.toolchain.ToolchainFactory; import org.apache.maven.toolchain.ToolchainManagerPrivate; import org.apache.maven.toolchain.ToolchainPrivate; @@ -41,6 +42,7 @@ import org.apache.maven.toolchain.model.ToolchainModel; import org.codehaus.plexus.util.xml.Xpp3Dom; +import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.ENV; import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.RUNTIME_NAME; import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.RUNTIME_VERSION; import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.VENDOR; @@ -89,6 +91,15 @@ public enum JdkMode { @Parameter(property = "toolchain.jdk.vendor") private String vendor; + /** + * The env constraint for the JDK toolchain to select. + * To match the constraint, an environment variable with the given name must point to the JDK. + * For example, if you define {@code JAVA11_HOME=~/jdks/my-jdk-11.0.1}, you can specify + * {@code env=JAVA11_HOME} to match the given JDK. + */ + @Parameter(property = "toolchain.jdk.env") + private String env; + /** * The matching mode, either {@code IfMatch} (the default), {@code IfSame}, or {@code Never}. * If {@code IfMatch} is used, a toolchain will not be selected if the running JDK does @@ -156,7 +167,7 @@ public void execute() throws MojoFailureException { } private void doExecute() throws MisconfiguredToolchainException, MojoFailureException { - if (version == null && runtimeName == null && runtimeVersion == null && vendor == null) { + if (version == null && runtimeName == null && runtimeVersion == null && vendor == null && env == null) { return; } @@ -165,21 +176,20 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce Optional.ofNullable(runtimeName).ifPresent(v -> requirements.put(RUNTIME_NAME, v)); Optional.ofNullable(runtimeVersion).ifPresent(v -> requirements.put(RUNTIME_VERSION, v)); Optional.ofNullable(vendor).ifPresent(v -> requirements.put(VENDOR, v)); + Optional.ofNullable(env).ifPresent(v -> requirements.put(ENV, v)); ToolchainModel currentJdkToolchainModel = discoverer.getCurrentJdkToolchain().orElse(null); ToolchainPrivate currentJdkToolchain = currentJdkToolchainModel != null ? factory.createToolchain(currentJdkToolchainModel) : null; - if (useJdk == JdkMode.IfMatch - && currentJdkToolchain != null - && currentJdkToolchain.matchesRequirements(requirements)) { + if (useJdk == JdkMode.IfMatch && currentJdkToolchain != null && matches(currentJdkToolchain, requirements)) { getLog().info("Not using an external toolchain as the current JDK matches the requirements."); return; } ToolchainPrivate toolchain = Stream.of(toolchainManager.getToolchainsForType(TOOLCHAIN_TYPE_JDK, session)) - .filter(tc -> tc.matchesRequirements(requirements)) + .filter(tc -> matches(tc, requirements)) .findFirst() .orElse(null); if (toolchain != null) { @@ -193,7 +203,7 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce for (ToolchainModel tcm : persistedToolchains.getToolchains()) { ToolchainPrivate tc = factory.createToolchain(tcm); - if (tc != null && tc.matchesRequirements(requirements)) { + if (tc != null && matches(tc, requirements)) { toolchain = tc; getLog().debug("Discovered matching JDK toolchain: " + toolchain); break; @@ -219,6 +229,35 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce getLog().info("Found matching JDK toolchain: " + toolchain); } + private boolean matches(ToolchainPrivate tc, Map requirements) { + ToolchainModel model = tc.getModel(); + for (Map.Entry req : requirements.entrySet()) { + String key = req.getKey(); + String reqVal = req.getValue(); + String tcVal = model.getProvides().getProperty(key); + if (tcVal == null) { + getLog().debug("Toolchain " + tc + " is missing required property: " + key); + return false; + } + if (!matches(key, reqVal, tcVal)) { + getLog().debug("Toolchain " + tc + " doesn't match required property: " + key); + return false; + } + } + return true; + } + + private boolean matches(String key, String reqVal, String tcVal) { + switch (key) { + case VERSION: + return RequirementMatcherFactory.createVersionMatcher(reqVal).matches(tcVal); + case ENV: + return reqVal.matches("(.*,|^)\\Q" + tcVal + "\\E(,.*|$)"); + default: + return RequirementMatcherFactory.createExactMatcher(reqVal).matches(tcVal); + } + } + private String getJdkHome(ToolchainPrivate toolchain) { return ((Xpp3Dom) toolchain.getModel().getConfiguration()) .getChild("jdkHome") diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index b840bbb..3166a75 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -127,23 +126,24 @@ public PersistedToolchains discoverToolchains(String comparator) { log.info("Found " + jdks.size() + " possible jdks: " + jdks); cacheModified = false; readCache(); - Map> flags = new HashMap<>(); + Map> flags = new HashMap<>(); Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty(JAVA_HOME))); - flags.computeIfAbsent(currentJdkHome, p -> new HashSet<>()).add(CURRENT); + flags.computeIfAbsent(currentJdkHome, p -> new HashMap<>()).put(CURRENT, "true"); // check environment variables for JAVA{xx}_HOME System.getenv().entrySet().stream() .filter(e -> e.getKey().startsWith("JAVA") && e.getKey().endsWith("_HOME")) - .map(e -> Paths.get(e.getValue())) - .map(ToolchainDiscoverer::getCanonicalPath) - .forEach(path -> - flags.computeIfAbsent(path, p -> new HashSet<>()).add(ENV)); + .forEach(e -> { + Path path = getCanonicalPath(Paths.get(e.getValue())); + Map f = flags.computeIfAbsent(path, p -> new HashMap<>()); + String val = f.getOrDefault(ENV, ""); + f.put(ENV, (val.isEmpty() ? "" : val + ",") + e.getKey()); + }); List tcs = jdks.parallelStream() .map(s -> { ToolchainModel tc = getToolchainModel(s); - for (String flag : flags.getOrDefault(s, Collections.emptySet())) { - tc.getProvides().setProperty(flag, "true"); - } + flags.getOrDefault(s, Collections.emptyMap()) + .forEach((k, v) -> tc.getProvides().setProperty(k, v)); String version = tc.getProvides().getProperty(VERSION); if (isLts(version)) { tc.getProvides().setProperty(LTS, "true"); From 9a8895f1881ed53b8203582adaae5016e844911e Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 13:03:11 +0100 Subject: [PATCH 06/20] Fix inverted matching tests --- .../maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java index 85da9f1..536ed76 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java @@ -250,11 +250,11 @@ private boolean matches(ToolchainPrivate tc, Map requirements) { private boolean matches(String key, String reqVal, String tcVal) { switch (key) { case VERSION: - return RequirementMatcherFactory.createVersionMatcher(reqVal).matches(tcVal); + return RequirementMatcherFactory.createVersionMatcher(tcVal).matches(reqVal); case ENV: return reqVal.matches("(.*,|^)\\Q" + tcVal + "\\E(,.*|$)"); default: - return RequirementMatcherFactory.createExactMatcher(reqVal).matches(tcVal); + return RequirementMatcherFactory.createExactMatcher(tcVal).matches(reqVal); } } From 6d4f2513af58ef1835dde40f350f9ee4d4a21a28 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 13:54:38 +0100 Subject: [PATCH 07/20] Remove useless log --- .../maven/plugins/toolchain/jdk/ToolchainDiscoverer.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 3166a75..d717d35 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -236,15 +236,12 @@ private Path getJdkHome(ToolchainModel toolchain) { } ToolchainModel getToolchainModel(Path jdk) { - log.debug("Computing model for " + jdk); - ToolchainModel model = cache.get(jdk); if (model == null) { model = doGetToolchainModel(jdk); cache.put(jdk, model); cacheModified = true; } - return model; } From 5a585f0e8625e5e34e1452a1c18a244f5b5c2686 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 14:01:35 +0100 Subject: [PATCH 08/20] Clean error messages --- .../toolchain/jdk/ToolchainDiscoverer.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index d717d35..8019547 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -247,13 +247,19 @@ ToolchainModel getToolchainModel(Path jdk) { ToolchainModel doGetToolchainModel(Path jdk) { Path java = jdk.resolve("bin/java"); - if (!java.toFile().canExecute()) { - java = jdk.resolve("bin/java.exe"); - if (!java.toFile().canExecute()) { - log.debug("JDK toolchain discovered at " + jdk + " will be ignored: unable to find java executable"); + if (!Files.exists(java)) { + java = jdk.resolve("bin\\java.exe"); + if (!Files.exists(java)) { + log.debug("JDK toolchain discovered at " + jdk + + " will be ignored: unable to find bin/java or bin\\java.exe"); return null; } } + if (!java.toFile().canExecute()) { + log.debug("JDK toolchain discovered at " + jdk + + " will be ignored: the bin/java or bin\\java.exe is not executable"); + return null; + } List lines; try { Path temp = Files.createTempFile("jdk-opts-", ".out"); @@ -268,7 +274,7 @@ ToolchainModel doGetToolchainModel(Path jdk) { Files.delete(temp); } } catch (IOException | InterruptedException e) { - log.debug("JDK toolchain discovered at " + jdk + " will be ignored: unable to execute java: " + e); + log.debug("JDK toolchain discovered at " + jdk + " will be ignored: error executing java: " + e); return null; } From 1d31b41eeef0ca57067154babe89a557d66396de Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 14:30:26 +0100 Subject: [PATCH 09/20] Restrict cache access to the 3 methods, cache always logs at debug level --- .../toolchain/jdk/ToolchainDiscoverer.java | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 8019547..6165b8e 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -124,7 +124,6 @@ public PersistedToolchains discoverToolchains(String comparator) { try { Set jdks = findJdks(); log.info("Found " + jdks.size() + " possible jdks: " + jdks); - cacheModified = false; readCache(); Map> flags = new HashMap<>(); Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty(JAVA_HOME))); @@ -152,9 +151,7 @@ public PersistedToolchains discoverToolchains(String comparator) { }) .sorted(getToolchainModelComparator(comparator)) .collect(Collectors.toList()); - if (this.cacheModified) { - writeCache(); - } + writeCache(); PersistedToolchains ps = new PersistedToolchains(); ps.setToolchains(tcs); return ps; @@ -179,6 +176,7 @@ private static boolean isLts(String version) { private void readCache() { try { cache = new ConcurrentHashMap<>(); + cacheModified = false; Path cacheFile = getCacheFile(); if (Files.isRegularFile(cacheFile)) { try (Reader r = Files.newBufferedReader(cacheFile)) { @@ -188,7 +186,7 @@ private void readCache() { .filter(tc -> { // If the bin/java executable is not available anymore, remove this TC if (!hasJavaC(getJdkHome(tc))) { - cacheModified = false; + cacheModified = true; return false; } else { return true; @@ -198,30 +196,43 @@ private void readCache() { } } } catch (IOException | XmlPullParserException e) { - log.warn("Error reading toolchains cache: " + e); + log.debug("Error reading toolchains cache: " + e, e); } } private void writeCache() { try { - Path cacheFile = getCacheFile(); - Files.createDirectories(cacheFile.getParent()); - try (Writer w = Files.newBufferedWriter(cacheFile)) { - PersistedToolchains pt = new PersistedToolchains(); - pt.setToolchains(cache.values().stream() - .map(tc -> { - ToolchainModel model = tc.clone(); - model.getProvides().remove(CURRENT); - model.getProvides().remove(ENV); - return model; - }) - .sorted(version().thenComparing(vendor())) - .collect(Collectors.toList())); - new MavenToolchainsXpp3Writer().write(w, pt); + if (cacheModified) { + Path cacheFile = getCacheFile(); + Files.createDirectories(cacheFile.getParent()); + try (Writer w = Files.newBufferedWriter(cacheFile)) { + PersistedToolchains pt = new PersistedToolchains(); + pt.setToolchains(cache.values().stream() + .map(tc -> { + ToolchainModel model = tc.clone(); + // Remove transient information + model.getProvides().remove(CURRENT); + model.getProvides().remove(ENV); + return model; + }) + .sorted(version().thenComparing(vendor())) + .collect(Collectors.toList())); + new MavenToolchainsXpp3Writer().write(w, pt); + } } } catch (IOException e) { - log.warn("Error writing toolchains cache: " + e); + log.debug("Error writing toolchains cache: " + e, e); + } + } + + ToolchainModel getToolchainModel(Path jdk) { + ToolchainModel model = cache.get(jdk); + if (model == null) { + model = doGetToolchainModel(jdk); + cache.put(jdk, model); + cacheModified = true; } + return model; } private static Path getCacheFile() { @@ -235,16 +246,6 @@ private Path getJdkHome(ToolchainModel toolchain) { return Paths.get(Objects.requireNonNull(jdk)); } - ToolchainModel getToolchainModel(Path jdk) { - ToolchainModel model = cache.get(jdk); - if (model == null) { - model = doGetToolchainModel(jdk); - cache.put(jdk, model); - cacheModified = true; - } - return model; - } - ToolchainModel doGetToolchainModel(Path jdk) { Path java = jdk.resolve("bin/java"); if (!Files.exists(java)) { From 53dddc69972ab9b7d71991b5b39e2f6caaf62f06 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 14:56:06 +0100 Subject: [PATCH 10/20] Disable test on jdk 8 --- .../toolchain/jdk/ToolchainDiscoverer.java | 2 +- .../toolchain/jdk/ToolchainDiscovererTest.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 6165b8e..b931f00 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -239,7 +239,7 @@ private static Path getCacheFile() { return Paths.get(System.getProperty(USER_HOME)).resolve(DISCOVERED_TOOLCHAINS_CACHE_XML); } - private Path getJdkHome(ToolchainModel toolchain) { + public Path getJdkHome(ToolchainModel toolchain) { Xpp3Dom dom = (Xpp3Dom) toolchain.getConfiguration(); Xpp3Dom javahome = dom != null ? dom.getChild(JDK_HOME) : null; String jdk = javahome != null ? javahome.getValue() : null; diff --git a/src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java b/src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java index f8c756d..11657c0 100644 --- a/src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java +++ b/src/test/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscovererTest.java @@ -19,7 +19,12 @@ package org.apache.maven.plugins.toolchain.jdk; import org.apache.maven.toolchain.model.PersistedToolchains; +import org.codehaus.plexus.util.xml.Xpp3Dom; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.CURRENT; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -27,12 +32,22 @@ public class ToolchainDiscovererTest { + final Logger logger = LoggerFactory.getLogger(getClass()); + @Test + @DisabledOnJre(JRE.JAVA_8) // java 8 often has jdk != jre void testDiscovery() { ToolchainDiscoverer discoverer = new ToolchainDiscoverer(); PersistedToolchains persistedToolchains = discoverer.discoverToolchains(); assertNotNull(persistedToolchains); + persistedToolchains.getToolchains().forEach(model -> { + logger.info(" - " + + ((Xpp3Dom) model.getConfiguration()).getChild("jdkHome").getValue()); + logger.info(" provides:"); + model.getProvides().forEach((k, v) -> logger.info(" " + k + ": " + v)); + }); + assertTrue(persistedToolchains.getToolchains().stream() .anyMatch(tc -> tc.getProvides().containsKey(CURRENT))); } From eba1a9f2b50e3185d73c00d6709915507e393840 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Feb 2024 16:15:18 +0100 Subject: [PATCH 11/20] Add some site doc --- .../DisplayDiscoveredJdkToolchainsMojo.java | 9 +- .../jdk/GenerateJdkToolchainsXmlMojo.java | 2 +- .../toolchain/jdk/SelectJdkToolchainMojo.java | 3 +- .../toolchain/jdk/ToolchainDiscoverer.java | 19 ++- src/site/apt/index.apt.vm | 21 ++- src/site/apt/toolchains/discovery.apt.vm | 148 ++++++++++++++++++ src/site/apt/toolchains/jdk.apt.vm | 1 + src/site/site.xml | 1 + 8 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 src/site/apt/toolchains/discovery.apt.vm diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java index 9dbdf22..bf3b46c 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/DisplayDiscoveredJdkToolchainsMojo.java @@ -29,8 +29,11 @@ import org.apache.maven.toolchain.model.ToolchainModel; import org.codehaus.plexus.util.xml.Xpp3Dom; +import static java.util.Comparator.comparing; +import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.SORTED_PROVIDES; + /** - * Run the JDK discoverer and display a summary of found toolchains. + * Discover the JDK toolchains and print them to the console. */ @Mojo(name = "display-discovered-jdk-toolchains", requiresProject = false) public class DisplayDiscoveredJdkToolchainsMojo extends AbstractMojo { @@ -64,7 +67,9 @@ public void execute() { getLog().info(" - " + ((Xpp3Dom) model.getConfiguration()).getChild("jdkHome").getValue()); getLog().info(" provides:"); - model.getProvides().forEach((k, v) -> getLog().info(" " + k + ": " + v)); + model.getProvides().entrySet().stream() + .sorted(comparing(e -> SORTED_PROVIDES.indexOf(e.getKey().toString()))) + .forEach(e -> getLog().info(" " + e.getKey() + ": " + e.getValue())); } } } diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java index 2becdf2..450eb95 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/GenerateJdkToolchainsXmlMojo.java @@ -35,7 +35,7 @@ import org.apache.maven.toolchain.model.io.xpp3.MavenToolchainsXpp3Writer; /** - * Run the JDK discovery mechanism and generates the toolchains XML. + * Run the JDK toolchain discovery mechanism and generates a toolchains XML. */ @Mojo(name = "generate-jdk-toolchains-xml", requiresProject = false) public class GenerateJdkToolchainsXmlMojo extends AbstractMojo { diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java index 536ed76..f0b2c85 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java @@ -49,8 +49,7 @@ import static org.apache.maven.plugins.toolchain.jdk.ToolchainDiscoverer.VERSION; /** - * Check that toolchains requirements are met by currently configured toolchains and - * store the selected toolchains in build context for later retrieval by other plugins. + * Discover JDK toolchains and select a matching one. */ @Mojo(name = "select-jdk-toolchain", defaultPhase = LifecyclePhase.VALIDATE) public class SelectJdkToolchainMojo extends AbstractMojo { diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index b931f00..ab49c57 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -28,6 +28,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -52,6 +53,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static java.util.Comparator.comparing; import static org.apache.maven.plugins.toolchain.jdk.SelectJdkToolchainMojo.TOOLCHAIN_TYPE_JDK; /** @@ -69,12 +71,15 @@ public class ToolchainDiscoverer { public static final String VENDOR_VERSION = "vendor.version"; public static final String[] PROPERTIES = {VERSION, RUNTIME_NAME, RUNTIME_VERSION, VENDOR, VENDOR_VERSION}; - public static final String DISCOVERED_TOOLCHAINS_CACHE_XML = ".m2/discovered-toolchains-cache.xml"; - public static final String CURRENT = "current"; public static final String ENV = "env"; public static final String LTS = "lts"; + public static final List SORTED_PROVIDES = Collections.unmodifiableList( + Arrays.asList(VERSION, RUNTIME_NAME, RUNTIME_VERSION, VENDOR, VENDOR_VERSION, CURRENT, LTS, ENV)); + + public static final String DISCOVERED_TOOLCHAINS_CACHE_XML = ".m2/discovered-toolchains-cache.xml"; + public static final String JDK_HOME = "jdkHome"; public static final String JAVA_HOME = "java.home"; @@ -338,23 +343,23 @@ private Comparator getComparator(String part) { } Comparator lts() { - return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().containsKey(LTS) ? -1 : +1); + return comparing((ToolchainModel tc) -> tc.getProvides().containsKey(LTS) ? -1 : +1); } Comparator vendor() { - return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().getProperty(VENDOR)); + return comparing((ToolchainModel tc) -> tc.getProvides().getProperty(VENDOR)); } Comparator env() { - return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().containsKey(ENV) ? -1 : +1); + return comparing((ToolchainModel tc) -> tc.getProvides().containsKey(ENV) ? -1 : +1); } Comparator current() { - return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().containsKey(CURRENT) ? -1 : +1); + return comparing((ToolchainModel tc) -> tc.getProvides().containsKey(CURRENT) ? -1 : +1); } Comparator version() { - return Comparator.comparing((ToolchainModel tc) -> tc.getProvides().getProperty(VERSION), (v1, v2) -> { + return comparing((ToolchainModel tc) -> tc.getProvides().getProperty(VERSION), (v1, v2) -> { String[] a = v1.split("\\."); String[] b = v2.split("\\."); int length = Math.min(a.length, b.length); diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm index c38055a..084697d 100644 --- a/src/site/apt/index.apt.vm +++ b/src/site/apt/index.apt.vm @@ -29,11 +29,28 @@ ${project.name} for example to make sure the plugins like compiler, surefire, javadoc, webstart etc. all use the same JDK for execution. Similarly to the maven-enforcer-plugin, it allows you to control environmental constraints in the build. +* Discovery mechanism + + Since version 3.2.0, a new toolchains mechanism is provided. This relies on an automatic discovery mechanism based + on an internal heuristic which tries to detect JDK from known locations. This mechanism is to be used with the + <<>> goal, read about the {{{./toolchains/discovery.html}discovery mechanism}} for more + informations. + * Goals Overview - The Toolchains plugin has one goal: + Since version 3.2.0, a new toolchains mechanism is provided. This relies on an automatic discovery mechanism based + on an internal heuristic which tries to detect JDK from known locations. This mechanism is to be used with the goal: + + * {{{./toolchain-mojo.html}toolchains:select-jdk-toolchain}} discover and selects a matching toolchain. + + Two helper goals are also provided: + + * {{{./toolchain-mojo.html}toolchains:display-discovered-jdk-toolchains}} displays discovered toolchains to the console. + * {{{./toolchain-mojo.html}toolchains:generate-jdk-toolchains-xml}} can be used to write a <<>> containing discovered JDKs. + + The previous <<>> goal is still available: - * {{{./toolchain-mojo.html}toolchains:toolchain}} checks that toolchains requirements are met by currently configured toolchains. + * {{{./toolchain-mojo.html}toolchains:toolchain}} checks that toolchains requirements are met by currently configured toolchains. * Usage diff --git a/src/site/apt/toolchains/discovery.apt.vm b/src/site/apt/toolchains/discovery.apt.vm new file mode 100644 index 0000000..b256f93 --- /dev/null +++ b/src/site/apt/toolchains/discovery.apt.vm @@ -0,0 +1,148 @@ +~~ 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. + + ------ + Discovery mechanism + ------ + Guillaume Nodet + ------ + 2024-02-28 + ------ + +JDK Toolchain discovery mechanism + + Since version 3.2.0, the plugin provides a heuristic to discover installed JDK toolchains, by looking + at known installation directories and at environment variables. + + The list of discovered toolchains can be easily displayed using the command + <<>>. + This will print something like: + ++---+ +[INFO] Discovered 10 JDK toolchains: +[INFO] - /Users/gnodet/.sdkman/candidates/java/21.0.2-graalce +[INFO] provides: +[INFO] version: 21.0.2 +[INFO] runtime.name: OpenJDK Runtime Environment +[INFO] runtime.version: 21.0.2+13-jvmci-23.1-b30 +[INFO] vendor: GraalVM Community +[INFO] vendor.version: GraalVM CE 21.0.2+13.1 +[INFO] current: true +[INFO] lts: true +[INFO] env: JAVA_HOME,JAVA21_HOME +... ++---+ + + If you have installed JDK using standard tools and they are not listed here, feel free + to {{{../issue-management.html}raise an issue}}. + + The discovery mechanism provides a few information for each discovered JDK: + + * <<>>: the JDK version + + * <<>>: the name of the JDK runtime + + * <<>>: the version of the JDK runtime + + * <<>>: the vendor name + + * <<>>: the vendor version + + * <<>>: set to <<>> if this is the running JDK + + * <<>>: set to <<>> if JDK version is a long-term supported version + + * <<>>: set to the comma separated list of <<>>> matching environment variables + + + The <<>> goal discovering and selecting a matching JDK. + The config below allows using the current JDK, or any other discovered JDK >= 17. + The benefit is that the current JDK can be kept for speed, but ensuring the usage of any JDK 17 or higher if + the current jdk is below the requirements. + ++---+ + + [17,) + + + + org.apache.maven.plugins + maven-toolchains-plugin + ${project.version} + + + + select-jdk-toolchain + + + + ++---+ + + If you use environment variables to configure your JDKs, you can use the following configuration to select + the toolchain which is configured using the <<>> environment variable. + ++---+ + + JAVA17_HOME + ++---+ + +* Selection mechanism + + Several properties can be used to express requirements to match against discovered JDK toolchains: + + * <<>> / <<>>: a version range such as <<<[17,18)>>> to match against the JDK version + + * <<>> / <<>> + + * <<>> / <<>> + + * <<>> / <<>> + + * <<>> / <<>>: the name of an environment variable that the JDK toolchain must match + + The <<>> can be used to define whether the current JDK can be used if it matches the requirements. + +* Sorting + + Multiple discovered JDK toolchains may match the above requirements. In such a case, you may want to express + preferences to use to sort the toolchains. This can be done using the <<>> configuration which is a + comma separated list of criterions amongst the following: + + * <<>>: prefer LTS toolchains + + * <<>>: prefer the current JDK + + * <<>>: prefer toolchains discovered from environment variables + + * <<>>: prefer higher JDK versions + + * <<>>: sort alphabetically by vendor name + + The default value is <<>>. + +* Toolchains XML file + + The generation of the <<>> file is not necessary to use discovered toolchains. + The <<>> will select a toolchain amongst explicitly configured toolchains and discovered + toolchains. The information for discovered toolchains are cached in <<<~/.m2/discovered-toolchains-cache.xml>>> file + by default, to speed up things. + + If you prefer, you can use the <<>> to generate a toolchain XML. This can be used in + conjunction to the <<>> configuration to disable the discovery and only use explicitly + configured toolchains. diff --git a/src/site/apt/toolchains/jdk.apt.vm b/src/site/apt/toolchains/jdk.apt.vm index 8e85e1e..3cbc486 100644 --- a/src/site/apt/toolchains/jdk.apt.vm +++ b/src/site/apt/toolchains/jdk.apt.vm @@ -25,6 +25,7 @@ JDK Toolchain +Note that this page refers to hand-written JDK toolchains. For a simpler setup, look at the {{{./discovery.html}discovery mechanism}}. * Toolchain Identification diff --git a/src/site/site.xml b/src/site/site.xml index 9ddf361..2475b2b 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -32,6 +32,7 @@ under the License. + From a0f733a4d4724ff1d7fc558a4592e714c19e0330 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 1 Mar 2024 16:12:26 +0100 Subject: [PATCH 12/20] Add support for windows' scoop installer --- .../maven/plugins/toolchain/jdk/ToolchainDiscoverer.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index ab49c57..a8893a7 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -411,6 +411,14 @@ private Set findJdks() { installedDirs.add(Paths.get(userHome, "Library/Java/JavaVirtualMachines")); } else if (win) { installedDirs.add(Paths.get("C:\\Program Files\\Java\\")); + Path scoop = Paths.get(userHome, "scoop/apps"); + if (Files.isDirectory(scoop)) { + try (Stream stream = Files.list(scoop)) { + stream.forEach(dirsToTest::add); + } catch (IOException e) { + // ignore + } + } } else { installedDirs.add(Paths.get("/usr/jdk")); installedDirs.add(Paths.get("/usr/java")); From f3b0b29ee8303ee9a884dbfe2979e8cdc0fa344b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 1 Mar 2024 16:32:09 +0100 Subject: [PATCH 13/20] Fixes from review --- .../toolchain/jdk/SelectJdkToolchainMojo.java | 8 +++--- src/site/apt/toolchains/discovery.apt.vm | 25 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java index f0b2c85..02efff9 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/SelectJdkToolchainMojo.java @@ -198,7 +198,7 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce if (toolchain == null && discoverToolchains) { getLog().debug("No matching toolchains configured, trying to discover JDK toolchains"); PersistedToolchains persistedToolchains = discoverer.discoverToolchains(comparator); - getLog().info("Discovered " + persistedToolchains.getToolchains().size() + " JDK toolchains"); + getLog().debug("Discovered " + persistedToolchains.getToolchains().size() + " JDK toolchains"); for (ToolchainModel tcm : persistedToolchains.getToolchains()) { ToolchainPrivate tc = factory.createToolchain(tcm); @@ -214,18 +214,18 @@ private void doExecute() throws MisconfiguredToolchainException, MojoFailureExce throw new MojoFailureException( "Cannot find matching toolchain definitions for the following toolchain types:" + requirements + System.lineSeparator() - + "Please make sure you define the required toolchains in your ~/.m2/toolchains.xml file."); + + "Define the required toolchains in your ~/.m2/toolchains.xml file."); } if (useJdk == JdkMode.IfSame && currentJdkToolchain != null && Objects.equals(getJdkHome(currentJdkToolchain), getJdkHome(toolchain))) { - getLog().info("Not using an external toolchain as the current JDK has been selected."); + getLog().debug("Not using an external toolchain as the current JDK has been selected."); return; } toolchainManager.storeToolchainToBuildContext(toolchain, session); - getLog().info("Found matching JDK toolchain: " + toolchain); + getLog().debug("Found matching JDK toolchain: " + toolchain); } private boolean matches(ToolchainPrivate tc, Map requirements) { diff --git a/src/site/apt/toolchains/discovery.apt.vm b/src/site/apt/toolchains/discovery.apt.vm index b256f93..e528bc6 100644 --- a/src/site/apt/toolchains/discovery.apt.vm +++ b/src/site/apt/toolchains/discovery.apt.vm @@ -47,10 +47,10 @@ JDK Toolchain discovery mechanism ... +---+ - If you have installed JDK using standard tools and they are not listed here, feel free - to {{{../issue-management.html}raise an issue}}. + If you have installed JDKs using known installers and they are not found by the plugin, + feel free to {{{../issue-management.html}raise an issue}}. - The discovery mechanism provides a few information for each discovered JDK: + The discovery mechanism provides information for each discovered JDK: * <<>>: the JDK version @@ -69,10 +69,9 @@ JDK Toolchain discovery mechanism * <<>>: set to the comma separated list of <<>>> matching environment variables - The <<>> goal discovering and selecting a matching JDK. + The <<>> goal finds a matching JDK. The config below allows using the current JDK, or any other discovered JDK >= 17. - The benefit is that the current JDK can be kept for speed, but ensuring the usage of any JDK 17 or higher if - the current jdk is below the requirements. + The current JDK can be kept for speed, but JDK 17 or higher will be used if the current JDK is older than 17. +---+ @@ -104,7 +103,7 @@ JDK Toolchain discovery mechanism * Selection mechanism - Several properties can be used to express requirements to match against discovered JDK toolchains: + Several properties can express requirements to match against discovered JDK toolchains: * <<>> / <<>>: a version range such as <<<[17,18)>>> to match against the JDK version @@ -120,9 +119,9 @@ JDK Toolchain discovery mechanism * Sorting - Multiple discovered JDK toolchains may match the above requirements. In such a case, you may want to express - preferences to use to sort the toolchains. This can be done using the <<>> configuration which is a - comma separated list of criterions amongst the following: + Multiple discovered JDK toolchains may satisfy the requirements. In such a case, you can express + preferences for sorting the toolchains. This can be done using the <<>> configuration which is a + comma separated list of criteria amongst the following: * <<>>: prefer LTS toolchains @@ -140,9 +139,9 @@ JDK Toolchain discovery mechanism The generation of the <<>> file is not necessary to use discovered toolchains. The <<>> will select a toolchain amongst explicitly configured toolchains and discovered - toolchains. The information for discovered toolchains are cached in <<<~/.m2/discovered-toolchains-cache.xml>>> file - by default, to speed up things. + toolchains. Discovered toolchains are cached in <<<~/.m2/discovered-toolchains-cache.xml>>> file + by default, to speed up builds. If you prefer, you can use the <<>> to generate a toolchain XML. This can be used in - conjunction to the <<>> configuration to disable the discovery and only use explicitly + conjunction with the <<>> configuration to disable discovery and only use explicitly configured toolchains. From 0e858055930cdd3e6687c61e421978539a947dd3 Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Mon, 4 Mar 2024 10:01:41 +0100 Subject: [PATCH 14/20] Check the currently selected version of any Scoop package --- .../maven/plugins/toolchain/jdk/ToolchainDiscoverer.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index a8893a7..b2892a3 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -411,10 +411,12 @@ private Set findJdks() { installedDirs.add(Paths.get(userHome, "Library/Java/JavaVirtualMachines")); } else if (win) { installedDirs.add(Paths.get("C:\\Program Files\\Java\\")); - Path scoop = Paths.get(userHome, "scoop/apps"); + Path scoop = Paths.get(userHome, "scoop", "apps"); if (Files.isDirectory(scoop)) { try (Stream stream = Files.list(scoop)) { - stream.forEach(dirsToTest::add); + // Scoop can install multiple versions of a Java distribution, we only take the one that is + // currently selected. + stream.map(path -> Paths.get(path.toString(), "current")).forEach(dirsToTest::add); } catch (IOException e) { // ignore } From afea2b23ca1ffb154b9e3ad0fc46c02377e79fba Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 6 Mar 2024 21:54:43 +0100 Subject: [PATCH 15/20] Avoid using hardcoded file separator --- .../toolchain/jdk/ToolchainDiscoverer.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index b2892a3..180d676 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -252,9 +252,9 @@ public Path getJdkHome(ToolchainModel toolchain) { } ToolchainModel doGetToolchainModel(Path jdk) { - Path java = jdk.resolve("bin/java"); + Path java = jdk.resolve("bin").resolve("java"); if (!Files.exists(java)) { - java = jdk.resolve("bin\\java.exe"); + java = jdk.resolve("bin").resolve("java.exe"); if (!Files.exists(java)) { log.debug("JDK toolchain discovered at " + jdk + " will be ignored: unable to find bin/java or bin\\java.exe"); @@ -391,27 +391,27 @@ private Set findJdks() { .filter(e -> e.getKey().startsWith("JAVA") && e.getKey().endsWith("_HOME")) .map(e -> Paths.get(e.getValue())) .forEach(dirsToTest::add); - final String userHome = System.getProperty(USER_HOME); + final Path userHome = Paths.get(System.getProperty(USER_HOME)); List installedDirs = new ArrayList<>(); // jdk installed by third - installedDirs.add(Paths.get(userHome, ".jdks")); - installedDirs.add(Paths.get(userHome, ".m2/jdks")); - installedDirs.add(Paths.get(userHome, ".sdkman/candidates/java")); - installedDirs.add(Paths.get(userHome, ".gradle/jdks")); - installedDirs.add(Paths.get(userHome, ".jenv/versions")); - installedDirs.add(Paths.get(userHome, ".jbang/cache/jdks")); - installedDirs.add(Paths.get(userHome, ".asdf/installs")); - installedDirs.add(Paths.get(userHome, ".jabba/jdk")); + installedDirs.add(userHome.resolve(".jdks")); + installedDirs.add(userHome.resolve(".m2").resolve("jdks")); + installedDirs.add(userHome.resolve(".sdkman").resolve("candidates").resolve("java")); + installedDirs.add(userHome.resolve(".gradle").resolve("jdks")); + installedDirs.add(userHome.resolve(".jenv").resolve("versions")); + installedDirs.add(userHome.resolve(".jbang").resolve("cache").resolve("jdks")); + installedDirs.add(userHome.resolve(".asdf").resolve("installs")); + installedDirs.add(userHome.resolve(".jabba").resolve("jdk")); // os related directories String osname = System.getProperty("os.name").toLowerCase(Locale.ROOT); boolean macos = osname.startsWith("mac"); boolean win = osname.startsWith("win"); if (macos) { installedDirs.add(Paths.get("/Library/Java/JavaVirtualMachines")); - installedDirs.add(Paths.get(userHome, "Library/Java/JavaVirtualMachines")); + installedDirs.add(userHome.resolve("Library/Java/JavaVirtualMachines")); } else if (win) { installedDirs.add(Paths.get("C:\\Program Files\\Java\\")); - Path scoop = Paths.get(userHome, "scoop", "apps"); + Path scoop = userHome.resolve("scoop").resolve("apps"); if (Files.isDirectory(scoop)) { try (Stream stream = Files.list(scoop)) { // Scoop can install multiple versions of a Java distribution, we only take the one that is @@ -432,7 +432,9 @@ private Set findJdks() { try (Stream stream = Files.list(dest)) { stream.forEach(dir -> { dirsToTest.add(dir); - dirsToTest.add(dir.resolve("Contents/Home")); + if (macos) { + dirsToTest.add(dir.resolve("Contents").resolve("Home")); + } }); } catch (IOException e) { // ignore @@ -447,6 +449,7 @@ private Set findJdks() { } private static boolean hasJavaC(Path subdir) { - return Files.exists(subdir.resolve("bin/javac")) || Files.exists(subdir.resolve("bin/javac.exe")); + return Files.exists(subdir.resolve(Paths.get("bin", "javac"))) + || Files.exists(subdir.resolve(Paths.get("bin" , "javac.exe"))); } } From 2a21af16ed479f136130c84cd07e2aab9f8b2f57 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 6 Mar 2024 21:55:42 +0100 Subject: [PATCH 16/20] For now, add all scoop children paths as possible install dirs --- .../maven/plugins/toolchain/jdk/ToolchainDiscoverer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 180d676..8e2cd9d 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -414,9 +414,7 @@ private Set findJdks() { Path scoop = userHome.resolve("scoop").resolve("apps"); if (Files.isDirectory(scoop)) { try (Stream stream = Files.list(scoop)) { - // Scoop can install multiple versions of a Java distribution, we only take the one that is - // currently selected. - stream.map(path -> Paths.get(path.toString(), "current")).forEach(dirsToTest::add); + stream.forEach(installedDirs::add); } catch (IOException e) { // ignore } From 450b405ac76d1ce48a2f374d85ace12d5e935274 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 7 Mar 2024 09:58:09 +0100 Subject: [PATCH 17/20] Cache found JDK in the singleton --- .../toolchain/jdk/ToolchainDiscoverer.java | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 8e2cd9d..071ecf0 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -88,8 +88,9 @@ public class ToolchainDiscoverer { private final Logger log = LoggerFactory.getLogger(getClass()); - private Map cache; - private boolean cacheModified; + private volatile Map cache; + private volatile boolean cacheModified; + private volatile Set foundJdks; /** * Build the model for the current JDK toolchain @@ -178,36 +179,38 @@ private static boolean isLts(String version) { || version.startsWith("25."); } - private void readCache() { - try { - cache = new ConcurrentHashMap<>(); - cacheModified = false; - Path cacheFile = getCacheFile(); - if (Files.isRegularFile(cacheFile)) { - try (Reader r = Files.newBufferedReader(cacheFile)) { - PersistedToolchains pt = new MavenToolchainsXpp3Reader().read(r, false); - cache = pt.getToolchains().stream() - // Remove stale entries - .filter(tc -> { - // If the bin/java executable is not available anymore, remove this TC - if (!hasJavaC(getJdkHome(tc))) { - cacheModified = true; - return false; - } else { - return true; - } - }) - .collect(Collectors.toConcurrentMap(this::getJdkHome, Function.identity())); + private synchronized void readCache() { + if (cache == null) { + try { + cache = new ConcurrentHashMap<>(); + cacheModified = false; + Path cacheFile = getCacheFile(); + if (Files.isRegularFile(cacheFile)) { + try (Reader r = Files.newBufferedReader(cacheFile)) { + PersistedToolchains pt = new MavenToolchainsXpp3Reader().read(r, false); + cache = pt.getToolchains().stream() + // Remove stale entries + .filter(tc -> { + // If the bin/java executable is not available anymore, remove this TC + if (!hasJavaC(getJdkHome(tc))) { + cacheModified = true; + return false; + } else { + return true; + } + }) + .collect(Collectors.toConcurrentMap(this::getJdkHome, Function.identity())); + } } + } catch (IOException | XmlPullParserException e) { + log.debug("Error reading toolchains cache: " + e, e); } - } catch (IOException | XmlPullParserException e) { - log.debug("Error reading toolchains cache: " + e, e); } } - private void writeCache() { - try { - if (cacheModified) { + private synchronized void writeCache() { + if (cacheModified) { + try { Path cacheFile = getCacheFile(); Files.createDirectories(cacheFile.getParent()); try (Writer w = Files.newBufferedWriter(cacheFile)) { @@ -224,9 +227,10 @@ private void writeCache() { .collect(Collectors.toList())); new MavenToolchainsXpp3Writer().write(w, pt); } + } catch (IOException e) { + log.debug("Error writing toolchains cache: " + e, e); } - } catch (IOException e) { - log.debug("Error writing toolchains cache: " + e, e); + cacheModified = false; } } @@ -383,6 +387,17 @@ Comparator version() { } private Set findJdks() { + if (foundJdks == null) { + synchronized (this) { + if (foundJdks == null) { + foundJdks = doFindJdks(); + } + } + } + return foundJdks; + } + + private Set doFindJdks() { List dirsToTest = new ArrayList<>(); // add current JDK dirsToTest.add(Paths.get(System.getProperty(JAVA_HOME))); From 8576b7938e945158aa4d889f61a85e2dea23175f Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 7 Mar 2024 09:58:21 +0100 Subject: [PATCH 18/20] Code style --- .../apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 071ecf0..3b57bbb 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -463,6 +463,6 @@ private Set doFindJdks() { private static boolean hasJavaC(Path subdir) { return Files.exists(subdir.resolve(Paths.get("bin", "javac"))) - || Files.exists(subdir.resolve(Paths.get("bin" , "javac.exe"))); + || Files.exists(subdir.resolve(Paths.get("bin", "javac.exe"))); } } From f0978ce29eff7c4573240bbfd9ef9fec45904f4e Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sat, 16 Mar 2024 01:12:24 +0100 Subject: [PATCH 19/20] Fix current JDK toolchain discovery --- .../maven/plugins/toolchain/jdk/ToolchainDiscoverer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 3b57bbb..50e6485 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -97,7 +97,7 @@ public class ToolchainDiscoverer { */ public Optional getCurrentJdkToolchain() { Path currentJdkHome = getCanonicalPath(Paths.get(System.getProperty(JAVA_HOME))); - if (hasJavaC(currentJdkHome)) { + if (!hasJavaC(currentJdkHome)) { // in case the current JVM is not a JDK return Optional.empty(); } @@ -106,7 +106,7 @@ public Optional getCurrentJdkToolchain() { Stream.of(PROPERTIES).forEach(k -> { String v = System.getProperty(JAVA + k); if (v != null) { - model.addProvide(k.substring(JAVA.length()), v); + model.addProvide(k, v); } }); model.addProvide(CURRENT, "true"); From 16d45627a276e6a8c52cfabe48f092d9d03b1c08 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 20 Mar 2024 08:18:37 +0100 Subject: [PATCH 20/20] Make the isLts check more robust --- .../maven/plugins/toolchain/jdk/ToolchainDiscoverer.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java index 50e6485..f138565 100644 --- a/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java +++ b/src/main/java/org/apache/maven/plugins/toolchain/jdk/ToolchainDiscoverer.java @@ -172,11 +172,8 @@ public PersistedToolchains discoverToolchains(String comparator) { } private static boolean isLts(String version) { - return version.startsWith("1.8.") - || version.startsWith("11.") - || version.startsWith("17.") - || version.startsWith("21.") - || version.startsWith("25."); + return Stream.of("1.8", "8", "11", "17", "21", "25") + .anyMatch(v -> version.equals(v) || version.startsWith(v + ".")); } private synchronized void readCache() {