diff --git a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/ArtifactVersionSelector.java b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/ArtifactVersionSelector.java new file mode 100644 index 00000000..f3385ec2 --- /dev/null +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/ArtifactVersionSelector.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-2024 Maveniverse Org. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + */ +package eu.maveniverse.maven.toolbox.shared; + +import java.util.List; +import java.util.function.BiFunction; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.version.Version; + +/** + * Selector that selects artifact version. + */ +public interface ArtifactVersionSelector extends BiFunction, String> { + /** + * Selector that returns artifact version. + */ + static ArtifactVersionSelector identity() { + return new ArtifactVersionSelector() { + @Override + public String apply(Artifact artifact, List versions) { + return artifact.getVersion(); + } + }; + } + + /** + * Selector that return plan last version. + */ + static ArtifactVersionSelector last() { + return new ArtifactVersionSelector() { + @Override + public String apply(Artifact artifact, List versions) { + return versions.isEmpty() + ? identity().apply(artifact, versions) + : versions.get(versions.size() - 1).toString(); + } + }; + } +} diff --git a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/LibYearSink.java b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/LibYearSink.java index d8301303..41b1a67a 100644 --- a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/LibYearSink.java +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/LibYearSink.java @@ -18,13 +18,11 @@ import java.time.LocalDate; import java.time.ZoneId; import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeSet; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.BiFunction; import org.apache.maven.search.api.SearchBackend; import org.apache.maven.search.api.SearchRequest; @@ -86,7 +84,7 @@ public static LibYearSink libYear( ToolboxSearchApiImpl toolboxSearchApi, boolean quiet, boolean allowSnapshots, - BiFunction, Version> versionSelector) { + BiFunction, String> versionSelector) { return new LibYearSink( output, context, toolboxResolver, toolboxSearchApi, quiet, allowSnapshots, versionSelector); } @@ -97,8 +95,8 @@ public static LibYearSink libYear( private final ToolboxSearchApiImpl toolboxSearchApi; private final boolean quiet; private final boolean allowSnapshots; - private final BiFunction, Version> versionSelector; - private final ConcurrentMap libYear; + private final BiFunction, String> versionSelector; + private final CopyOnWriteArraySet artifacts; private LibYearSink( Output output, @@ -107,7 +105,7 @@ private LibYearSink( ToolboxSearchApiImpl toolboxSearchApi, boolean quiet, boolean allowSnapshots, - BiFunction, Version> versionSelector) { + BiFunction, String> versionSelector) { this.output = requireNonNull(output, "output"); this.context = requireNonNull(context, "context"); this.toolboxResolver = requireNonNull(toolboxResolver, "toolboxResolver"); @@ -115,26 +113,27 @@ private LibYearSink( this.quiet = quiet; this.allowSnapshots = allowSnapshots; this.versionSelector = requireNonNull(versionSelector); - this.libYear = new ConcurrentHashMap<>(); + this.artifacts = new CopyOnWriteArraySet<>(); } - public Map getLibYear() { - return libYear; + @SuppressWarnings("unchecked") + public ConcurrentMap getLibYear() { + return (ConcurrentMap) + context.repositorySystemSession().getData().computeIfAbsent(LibYear.class, ConcurrentHashMap::new); } @Override public void accept(Artifact artifact) throws IOException { requireNonNull(artifact, "artifact"); - libYear.computeIfAbsent(artifact, a -> { + getLibYear().computeIfAbsent(artifact, a -> { String currentVersion = artifact.getVersion(); Instant currentVersionInstant = null; String latestVersion = currentVersion; Instant latestVersionInstant = null; try { currentVersionInstant = artifactPublishDate(artifact); - latestVersion = versionSelector - .apply(artifact, toolboxResolver.findNewerVersions(artifact, allowSnapshots)) - .toString(); + latestVersion = + versionSelector.apply(artifact, toolboxResolver.findNewerVersions(artifact, allowSnapshots)); latestVersionInstant = artifactPublishDate(artifact.setVersion(latestVersion)); } catch (VersionRangeResolutionException e) { // ignore @@ -143,30 +142,31 @@ public void accept(Artifact artifact) throws IOException { } return new LibYear(currentVersion, currentVersionInstant, latestVersion, latestVersionInstant); }); + artifacts.add(artifact); } @Override public void close() throws DeploymentException { float totalLibYears = 0; int totalLibOutdated = 0; - TreeSet timedOnes = new TreeSet<>(Collections.reverseOrder()); + TreeMap> timedOnes = new TreeMap<>(Collections.reverseOrder()); TreeSet outdated = new TreeSet<>(); if (!quiet) { - for (Map.Entry entry : getLibYear().entrySet()) { - if (entry.getValue() != null) { - LibYear value = entry.getValue(); - if (Objects.equals(value.getCurrentVersion(), value.getLatestVersion())) { + for (Artifact artifact : artifacts) { + LibYear libYear = getLibYear().get(artifact); + if (libYear != null) { + if (Objects.equals(libYear.getCurrentVersion(), libYear.getLatestVersion())) { continue; } totalLibOutdated++; - LocalDate currentVersionDate = value.getCurrentVersionInstant() != null - ? value.getCurrentVersionInstant() + LocalDate currentVersionDate = libYear.getCurrentVersionInstant() != null + ? libYear.getCurrentVersionInstant() .atZone(ZoneId.systemDefault()) .toLocalDate() : null; - LocalDate latestVersionDate = value.getLatestVersionInstant() != null - ? value.getLatestVersionInstant() + LocalDate latestVersionDate = libYear.getLatestVersionInstant() != null + ? libYear.getLatestVersionInstant() .atZone(ZoneId.systemDefault()) .toLocalDate() : null; @@ -175,30 +175,36 @@ public void close() throws DeploymentException { long libWeeksOutdated = ChronoUnit.WEEKS.between(currentVersionDate, latestVersionDate); float libYearsOutdated = libWeeksOutdated / 52f; totalLibYears += libYearsOutdated; - timedOnes.add(String.format( - "%.2f years from %s %s (%s) => %s (%s)", - libYearsOutdated, - ArtifactIdUtils.toVersionlessId(entry.getKey()), - value.getCurrentVersion(), - currentVersionDate, - value.getLatestVersion(), - latestVersionDate)); + timedOnes + .computeIfAbsent(libYearsOutdated, k -> new CopyOnWriteArrayList<>()) + .add(String.format( + "%.2f years from %s %s (%s) => %s (%s)", + libYearsOutdated, + ArtifactIdUtils.toVersionlessId(artifact), + libYear.getCurrentVersion(), + currentVersionDate, + libYear.getLatestVersion(), + latestVersionDate)); } else { outdated.add(String.format( "%s %s (?) => %s (?)", - ArtifactIdUtils.toVersionlessId(entry.getKey()), - value.getCurrentVersion(), - value.getLatestVersion())); + ArtifactIdUtils.toVersionlessId(artifact), + libYear.getCurrentVersion(), + libYear.getLatestVersion())); } } } } String indent = ""; - output.normal("{}Outdated versions with known age", indent); - timedOnes.forEach(l -> output.normal("{}{}", indent, l)); + if (!timedOnes.isEmpty()) { + output.normal("{}Outdated versions with known age", indent); + } + timedOnes.values().stream().flatMap(Collection::stream).forEach(l -> output.normal("{}{}", indent, l)); output.normal("{}", indent); - output.normal("{}Outdated versions", indent); + if (!outdated.isEmpty()) { + output.normal("{}Outdated versions", indent); + } outdated.forEach(l -> output.normal("{}{}", indent, l)); output.normal("{}", indent); output.normal( diff --git a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImpl.java b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImpl.java index d206aa4c..3b228ebf 100644 --- a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImpl.java +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImpl.java @@ -25,6 +25,7 @@ import eu.maveniverse.maven.toolbox.shared.ArtifactMatcher; import eu.maveniverse.maven.toolbox.shared.ArtifactNameMapper; import eu.maveniverse.maven.toolbox.shared.ArtifactSink; +import eu.maveniverse.maven.toolbox.shared.ArtifactVersionSelector; import eu.maveniverse.maven.toolbox.shared.DependencyMatcher; import eu.maveniverse.maven.toolbox.shared.Output; import eu.maveniverse.maven.toolbox.shared.ResolutionRoot; @@ -543,9 +544,16 @@ private boolean doResolveTransitive( resolutionRoot.getManagedDependencies()); List adjustedResults = resolutionRoot.isLoad() ? dependencyResult.getArtifactResults() - : dependencyResult - .getArtifactResults() - .subList(1, dependencyResult.getArtifactResults().size() - 1); + : (dependencyResult.getArtifactResults().size() == 1 + ? Collections.emptyList() + : dependencyResult + .getArtifactResults() + .subList( + 1, + dependencyResult + .getArtifactResults() + .size() + - 1)); artifactSink.accept( adjustedResults.stream().map(ArtifactResult::getArtifact).collect(Collectors.toList())); @@ -802,24 +810,26 @@ public boolean libYear( boolean allowSnapshots, Output output) throws Exception { - for (ResolutionRoot resolutionRoot : resolutionRoots) { - doResolveTransitive( - resolutionScope, - resolutionRoot, - false, - false, - false, - LibYearSink.libYear( - output, - context, - toolboxResolver, - toolboxSearchApi, - quiet, - allowSnapshots, - (a, l) -> l.get(l.size() - 1)), - output); + try (ArtifactSink sink = LibYearSink.libYear( + output, + context, + toolboxResolver, + toolboxSearchApi, + quiet, + allowSnapshots, + ArtifactVersionSelector.last())) { + for (ResolutionRoot resolutionRoot : resolutionRoots) { + doResolveTransitive( + resolutionScope, + resolutionRoot, + false, + false, + false, + ArtifactSinks.nonClosingArtifactSink(sink), + output); + } } - return !resolutionRoots.isEmpty(); + return true; } // Utils diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/MPMojoSupport.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/MPMojoSupport.java index 3b3ed41a..5fd68d9e 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/MPMojoSupport.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/MPMojoSupport.java @@ -9,6 +9,7 @@ import eu.maveniverse.maven.toolbox.shared.ResolutionRoot; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; @@ -55,4 +56,14 @@ protected ResolutionRoot projectAsResolutionRoot() { } return builder.build(); } + + protected boolean isReactorDependency(Dependency dependency) { + return mavenSession.getAllProjects().stream() + .anyMatch(p -> Objects.equals( + p.getGroupId(), dependency.getArtifact().getGroupId()) + && Objects.equals( + p.getArtifactId(), dependency.getArtifact().getArtifactId()) + && Objects.equals( + p.getVersion(), dependency.getArtifact().getVersion())); + } } diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/gav/GavLibYearMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/gav/GavLibYearMojo.java index 921b6639..367275f3 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/gav/GavLibYearMojo.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/gav/GavLibYearMojo.java @@ -48,9 +48,31 @@ public class GavLibYearMojo extends GavSearchMojoSupport { @Parameter(property = "boms") private String boms; + /** + * Make libyear quiet. + */ + @CommandLine.Option( + names = {"--quiet"}, + description = "Make command quiet") + @Parameter(property = "quiet", defaultValue = "false") + private boolean quiet; + + /** + * Make libyear allow to take into account snapshots. + */ + @CommandLine.Option( + names = {"--allowSnapshots"}, + description = "Make libyear allow to take into account snapshots") + @Parameter(property = "allowSnapshots", defaultValue = "false") + private boolean allowSnapshots; + @Override protected boolean doExecute(Output output, ToolboxCommando toolboxCommando) throws Exception { return toolboxCommando.libYear( - ResolutionScope.parse(scope), toolboxCommando.loadGavs(slurp(gav), slurp(boms)), false, false, output); + ResolutionScope.parse(scope), + toolboxCommando.loadGavs(slurp(gav), slurp(boms)), + quiet, + allowSnapshots, + output); } } diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyMojo.java index 7c44d2de..a023e6cf 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyMojo.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyMojo.java @@ -30,7 +30,7 @@ public final class CopyMojo extends MPMojoSupport { /** * The dependency matcher spec. */ - @Parameter(property = "depSpec", required = true) + @Parameter(property = "depSpec", defaultValue = "any()") private String depSpec; @Override diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyTransitiveMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyTransitiveMojo.java index 71dea7cb..307317f1 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyTransitiveMojo.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/CopyTransitiveMojo.java @@ -37,7 +37,7 @@ public final class CopyTransitiveMojo extends MPMojoSupport { /** * The dependency matcher spec. */ - @Parameter(property = "depSpec", required = true) + @Parameter(property = "depSpec", defaultValue = "any()") private String depSpec; @Override diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/LibYearMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/LibYearMojo.java new file mode 100644 index 00000000..efadd006 --- /dev/null +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/LibYearMojo.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023-2024 Maveniverse Org. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + */ +package eu.maveniverse.maven.toolbox.plugin.mp; + +import eu.maveniverse.maven.toolbox.plugin.MPMojoSupport; +import eu.maveniverse.maven.toolbox.shared.Output; +import eu.maveniverse.maven.toolbox.shared.ResolutionRoot; +import eu.maveniverse.maven.toolbox.shared.ResolutionScope; +import eu.maveniverse.maven.toolbox.shared.ToolboxCommando; +import java.util.stream.Collectors; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +/** + * Calculates "libyear" for Maven project transitively. + */ +@Mojo(name = "libyear", threadSafe = true) +public class LibYearMojo extends MPMojoSupport { + /** + * Resolution scope to resolve (default 'runtime'). + */ + @Parameter(property = "scope", defaultValue = "runtime", required = true) + private String scope; + + /** + * The dependency matcher spec. + */ + @Parameter(property = "depSpec", defaultValue = "any()") + private String depSpec; + + /** + * Make libyear quiet. + */ + @Parameter(property = "quiet", defaultValue = "false") + private boolean quiet; + + /** + * Make libyear allow to take into account snapshots. + */ + @Parameter(property = "allowSnapshots", defaultValue = "false") + private boolean allowSnapshots; + + @Override + protected boolean doExecute(Output output, ToolboxCommando toolboxCommando) throws Exception { + ResolutionRoot project = projectAsResolutionRoot(); + return toolboxCommando.libYear( + ResolutionScope.parse(scope), + projectAsResolutionRoot().getDependencies().stream() + .filter(toolboxCommando.parseDependencyMatcherSpec(depSpec)) + .filter(d -> !isReactorDependency(d)) + .map(d -> ResolutionRoot.ofLoaded(d.getArtifact()) + .withManagedDependencies(project.getManagedDependencies()) + .build()) + .collect(Collectors.toList()), + quiet, + allowSnapshots, + output); + } +} diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveMojo.java index 5a64724f..080512c5 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveMojo.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveMojo.java @@ -21,7 +21,7 @@ /** * Resolves transitively given project build plugin. */ -@Mojo(name = "plugin-resolve", requiresProject = false, threadSafe = true) +@Mojo(name = "plugin-resolve", threadSafe = true) public class PluginResolveMojo extends MPPluginMojoSupport { /** * Resolve sources JAR as well (derive coordinates from GAV). diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveTransitiveMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveTransitiveMojo.java index a25dfc29..f30aa082 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveTransitiveMojo.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/PluginResolveTransitiveMojo.java @@ -20,7 +20,7 @@ /** * Resolves transitively given project build plugin. */ -@Mojo(name = "plugin-resolve-transitive", requiresProject = false, threadSafe = true) +@Mojo(name = "plugin-resolve-transitive", threadSafe = true) public class PluginResolveTransitiveMojo extends MPPluginMojoSupport { /** * The resolution scope to resolve, accepted values are "runtime", "compile", "test", etc. diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveMojo.java index 67ed3923..3f48acd6 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveMojo.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveMojo.java @@ -18,12 +18,12 @@ /** * Resolves selected dependencies. */ -@Mojo(name = "resolve", requiresProject = false, threadSafe = true) +@Mojo(name = "resolve", threadSafe = true) public class ResolveMojo extends MPMojoSupport { /** * The dependency matcher spec. */ - @Parameter(property = "depSpec", required = true) + @Parameter(property = "depSpec", defaultValue = "any()") private String depSpec; /** diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveTransitiveMojo.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveTransitiveMojo.java index 9718a72e..92bcc23a 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveTransitiveMojo.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/mp/ResolveTransitiveMojo.java @@ -19,7 +19,7 @@ /** * Resolves transitively selected dependencies. */ -@Mojo(name = "resolve-transitive", requiresProject = false, threadSafe = true) +@Mojo(name = "resolve-transitive", threadSafe = true) public class ResolveTransitiveMojo extends MPMojoSupport { /** * The resolution scope to resolve, accepted values are "runtime", "compile", "test", etc. @@ -30,7 +30,7 @@ public class ResolveTransitiveMojo extends MPMojoSupport { /** * The dependency matcher spec. */ - @Parameter(property = "depSpec", required = true) + @Parameter(property = "depSpec", defaultValue = "any()") private String depSpec; /**