From 3c7ad599e01de672265340da180290bb918ae6e4 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 15 May 2024 20:57:52 +0200 Subject: [PATCH] Libyear command (#33) See https://libyear.com/ Example: ``` [cstamas@angeleyes toolbox (libyear)]$ java -jar toolbox/target/toolbox-0.1.11-SNAPSHOT-cli.jar libyear org.apache.maven:maven-core:3.9.6 Outdated versions with known age 4.17 years from org.codehaus.plexus:plexus-component-annotations:jar 2.1.0 (2019-10-23) => 2.2.0 (2023-12-24) 2.73 years from org.apache.commons:commons-lang3:jar 3.12.0 (2021-02-26) => 3.14.0 (2023-11-18) 2.12 years from org.codehaus.plexus:plexus-cipher:jar 2.0 (2021-09-08) => 2.1.0 (2023-10-22) 2.04 years from org.apache.maven.shared:maven-shared-utils:jar 3.3.4 (2021-04-26) => 3.4.2 (2023-05-11) 1.90 years from org.slf4j:slf4j-api:jar 1.7.36 (2022-02-08) => 2.1.0-alpha1 (2024-01-02) 1.29 years from com.google.inject:guice:jar 5.1.0 (2022-01-24) => 7.0.0 (2023-05-12) 1.12 years from org.codehaus.plexus:plexus-utils:jar 3.5.1 (2023-03-02) => 4.0.1 (2024-04-13) 0.42 years from org.apache.maven.resolver:maven-resolver-util:jar 1.9.18 (2023-11-22) => 2.0.0-alpha-11 (2024-04-26) 0.42 years from org.apache.maven.resolver:maven-resolver-spi:jar 1.9.18 (2023-11-22) => 2.0.0-alpha-11 (2024-04-26) 0.42 years from org.apache.maven.resolver:maven-resolver-named-locks:jar 1.9.18 (2023-11-22) => 2.0.0-alpha-11 (2024-04-26) 0.42 years from org.apache.maven.resolver:maven-resolver-impl:jar 1.9.18 (2023-11-22) => 2.0.0-alpha-11 (2024-04-26) 0.42 years from org.apache.maven.resolver:maven-resolver-api:jar 1.9.18 (2023-11-22) => 2.0.0-alpha-11 (2024-04-26) 0.27 years from org.apache.maven:maven-settings:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-settings-builder:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-resolver-provider:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-repository-metadata:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-plugin-api:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-model:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-model-builder:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-core:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-builder-support:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) 0.27 years from org.apache.maven:maven-artifact:jar 3.9.6 (2023-11-28) => 4.0.0-alpha-13 (2024-03-06) Outdated versions com.google.guava:failureaccess:jar 1.0.1 (?) => 1.0.2 (?) com.google.guava:guava:jar 32.0.1-jre (?) => 33.2.0-jre (?) org.codehaus.plexus:plexus-classworlds:jar 2.7.0 (?) => 2.8.0 (?) org.codehaus.plexus:plexus-interpolation:jar 1.26 (?) => 1.27 (?) Total of 20.17 years from 26 outdated dependencies [cstamas@angeleyes toolbox (libyear)]$ ``` --- .../maven/toolbox/shared/ToolboxCommando.java | 10 + .../toolbox/shared/internal/LibYearSink.java | 230 ++++++++++++++++++ .../shared/internal/ToolboxCommandoImpl.java | 140 +++++++---- .../shared/internal/ToolboxResolverImpl.java | 25 ++ .../internal/ToolboxCommandoImplTest.java | 31 +++ .../maveniverse/maven/toolbox/plugin/CLI.java | 1 + .../toolbox/plugin/gav/GavLibYearMojo.java | 56 +++++ 7 files changed, 445 insertions(+), 48 deletions(-) create mode 100644 shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/LibYearSink.java create mode 100644 shared/src/test/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImplTest.java create mode 100644 toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/gav/GavLibYearMojo.java diff --git a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/ToolboxCommando.java b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/ToolboxCommando.java index 2c00f401..268b47e1 100644 --- a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/ToolboxCommando.java +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/ToolboxCommando.java @@ -190,4 +190,14 @@ boolean exists( boolean search(RemoteRepository remoteRepository, String expression, Output output) throws IOException; boolean verify(RemoteRepository remoteRepository, String gav, String sha1, Output output) throws IOException; + + // Various + + boolean libYear( + ResolutionScope resolutionScope, + Collection resolutionRoots, + boolean quiet, + boolean allowSnapshots, + Output output) + throws Exception; } 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 new file mode 100644 index 00000000..d8301303 --- /dev/null +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/LibYearSink.java @@ -0,0 +1,230 @@ +/* + * 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.internal; + +import static java.util.Objects.requireNonNull; + +import eu.maveniverse.maven.mima.context.Context; +import eu.maveniverse.maven.toolbox.shared.ArtifactSink; +import eu.maveniverse.maven.toolbox.shared.Output; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.time.Instant; +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.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; +import org.apache.maven.search.api.SearchBackend; +import org.apache.maven.search.api.SearchRequest; +import org.apache.maven.search.api.SearchResponse; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.deployment.DeploymentException; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.VersionRangeResolutionException; +import org.eclipse.aether.util.artifact.ArtifactIdUtils; +import org.eclipse.aether.version.Version; + +/** + * Construction to calculate "libyear". + * + * @see libyear + */ +public final class LibYearSink implements ArtifactSink { + public static final class LibYear { + private final String currentVersion; + private final Instant currentVersionInstant; + private final String latestVersion; + private final Instant latestVersionInstant; + + private LibYear( + String currentVersion, + Instant currentVersionInstant, + String latestVersion, + Instant latestVersionInstant) { + this.currentVersion = currentVersion; + this.currentVersionInstant = currentVersionInstant; + this.latestVersion = latestVersion; + this.latestVersionInstant = latestVersionInstant; + } + + public String getCurrentVersion() { + return currentVersion; + } + + public Instant getCurrentVersionInstant() { + return currentVersionInstant; + } + + public String getLatestVersion() { + return latestVersion; + } + + public Instant getLatestVersionInstant() { + return latestVersionInstant; + } + } + + /** + * Creates libYear sink. + */ + public static LibYearSink libYear( + Output output, + Context context, + ToolboxResolverImpl toolboxResolver, + ToolboxSearchApiImpl toolboxSearchApi, + boolean quiet, + boolean allowSnapshots, + BiFunction, Version> versionSelector) { + return new LibYearSink( + output, context, toolboxResolver, toolboxSearchApi, quiet, allowSnapshots, versionSelector); + } + + private final Output output; + private final Context context; + private final ToolboxResolverImpl toolboxResolver; + private final ToolboxSearchApiImpl toolboxSearchApi; + private final boolean quiet; + private final boolean allowSnapshots; + private final BiFunction, Version> versionSelector; + private final ConcurrentMap libYear; + + private LibYearSink( + Output output, + Context context, + ToolboxResolverImpl toolboxResolver, + ToolboxSearchApiImpl toolboxSearchApi, + boolean quiet, + boolean allowSnapshots, + BiFunction, Version> versionSelector) { + this.output = requireNonNull(output, "output"); + this.context = requireNonNull(context, "context"); + this.toolboxResolver = requireNonNull(toolboxResolver, "toolboxResolver"); + this.toolboxSearchApi = requireNonNull(toolboxSearchApi, "toolboxSearchApi"); + this.quiet = quiet; + this.allowSnapshots = allowSnapshots; + this.versionSelector = requireNonNull(versionSelector); + this.libYear = new ConcurrentHashMap<>(); + } + + public Map getLibYear() { + return libYear; + } + + @Override + public void accept(Artifact artifact) throws IOException { + requireNonNull(artifact, "artifact"); + libYear.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(); + latestVersionInstant = artifactPublishDate(artifact.setVersion(latestVersion)); + } catch (VersionRangeResolutionException e) { + // ignore + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return new LibYear(currentVersion, currentVersionInstant, latestVersion, latestVersionInstant); + }); + } + + @Override + public void close() throws DeploymentException { + float totalLibYears = 0; + int totalLibOutdated = 0; + TreeSet timedOnes = new TreeSet<>(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())) { + continue; + } + totalLibOutdated++; + + LocalDate currentVersionDate = value.getCurrentVersionInstant() != null + ? value.getCurrentVersionInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate() + : null; + LocalDate latestVersionDate = value.getLatestVersionInstant() != null + ? value.getLatestVersionInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate() + : null; + + if (currentVersionDate != null && latestVersionDate != null) { + 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)); + } else { + outdated.add(String.format( + "%s %s (?) => %s (?)", + ArtifactIdUtils.toVersionlessId(entry.getKey()), + value.getCurrentVersion(), + value.getLatestVersion())); + } + } + } + } + + String indent = ""; + output.normal("{}Outdated versions with known age", indent); + timedOnes.forEach(l -> output.normal("{}{}", indent, l)); + output.normal("{}", indent); + output.normal("{}Outdated versions", indent); + outdated.forEach(l -> output.normal("{}{}", indent, l)); + output.normal("{}", indent); + output.normal( + "{}Total of {} years from {} outdated dependencies", + indent, + String.format("%.2f", totalLibYears), + totalLibOutdated); + output.normal("{}", indent); + } + + private Instant artifactPublishDate(Artifact artifact) throws IOException { + for (RemoteRepository remoteRepository : context.remoteRepositories()) { + try (SearchBackend backend = toolboxSearchApi.getSmoBackend(remoteRepository)) { + SearchRequest searchRequest = new SearchRequest(toolboxSearchApi.toSmoQuery(artifact)); + SearchResponse searchResponse = backend.search(searchRequest); + if (searchResponse.getCurrentHits() > 0) { + Long lastUpdated = + searchResponse.getPage().iterator().next().getLastUpdated(); + if (lastUpdated != null) { + return Instant.ofEpochMilli(lastUpdated); + } + } + } catch (IllegalArgumentException e) { + // most likely not SMO service (remote repo is not CENTRAL); ignore + } + } + return null; + } +} 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 f2222284..d206aa4c 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 @@ -510,62 +510,76 @@ public boolean resolveTransitive( try (ArtifactSink artifactSink = ArtifactSinks.teeArtifactSink(sink, ArtifactSinks.statArtifactSink(0, false, output))) { for (ResolutionRoot resolutionRoot : resolutionRoots) { - output.verbose("Resolving {}", resolutionRoot.getArtifact()); - resolutionRoot = toolboxResolver.loadRoot(resolutionRoot); - DependencyResult dependencyResult = toolboxResolver.resolve( + doResolveTransitive( resolutionScope, - resolutionRoot.getArtifact(), - resolutionRoot.getDependencies(), - resolutionRoot.getManagedDependencies()); - List adjustedResults = resolutionRoot.isLoad() - ? dependencyResult.getArtifactResults() - : dependencyResult - .getArtifactResults() - .subList( - 1, dependencyResult.getArtifactResults().size() - 1); + resolutionRoot, + sources, + javadoc, + signature, + ArtifactSinks.teeArtifactSink( + nonClosingArtifactSink(artifactSink), ArtifactSinks.statArtifactSink(1, true, output)), + output); + } + return !resolutionRoots.isEmpty(); + } + } - try (ArtifactSink batchSink = ArtifactSinks.teeArtifactSink( - nonClosingArtifactSink(artifactSink), ArtifactSinks.statArtifactSink(1, true, output))) { - batchSink.accept(adjustedResults.stream() - .map(ArtifactResult::getArtifact) - .collect(Collectors.toList())); + private boolean doResolveTransitive( + ResolutionScope resolutionScope, + ResolutionRoot resolutionRoot, + boolean sources, + boolean javadoc, + boolean signature, + ArtifactSink sink, + Output output) + throws Exception { + try (ArtifactSink artifactSink = sink) { + output.verbose("Resolving {}", resolutionRoot.getArtifact()); + resolutionRoot = toolboxResolver.loadRoot(resolutionRoot); + DependencyResult dependencyResult = toolboxResolver.resolve( + resolutionScope, + resolutionRoot.getArtifact(), + resolutionRoot.getDependencies(), + resolutionRoot.getManagedDependencies()); + List adjustedResults = resolutionRoot.isLoad() + ? dependencyResult.getArtifactResults() + : dependencyResult + .getArtifactResults() + .subList(1, dependencyResult.getArtifactResults().size() - 1); + artifactSink.accept( + adjustedResults.stream().map(ArtifactResult::getArtifact).collect(Collectors.toList())); - if (sources || javadoc || signature) { - HashSet subartifacts = new HashSet<>(); - adjustedResults.stream() + if (sources || javadoc || signature) { + HashSet subartifacts = new HashSet<>(); + adjustedResults.stream().map(ArtifactResult::getArtifact).forEach(a -> { + if (sources && a.getClassifier().isEmpty()) { + subartifacts.add(new SubArtifact(a, "sources", "jar")); + } + if (javadoc && a.getClassifier().isEmpty()) { + subartifacts.add(new SubArtifact(a, "javadoc", "jar")); + } + if (signature && !a.getExtension().endsWith(".asc")) { + subartifacts.add(new SubArtifact(a, "*", "*.asc")); + } + }); + if (!subartifacts.isEmpty()) { + output.verbose("Resolving (best effort) {}", subartifacts); + try { + List subartifactResults = toolboxResolver.resolveArtifacts(subartifacts); + artifactSink.accept(subartifactResults.stream() .map(ArtifactResult::getArtifact) - .forEach(a -> { - if (sources && a.getClassifier().isEmpty()) { - subartifacts.add(new SubArtifact(a, "sources", "jar")); - } - if (javadoc && a.getClassifier().isEmpty()) { - subartifacts.add(new SubArtifact(a, "javadoc", "jar")); - } - if (signature && !a.getExtension().endsWith(".asc")) { - subartifacts.add(new SubArtifact(a, "*", "*.asc")); - } - }); - if (!subartifacts.isEmpty()) { - output.verbose("Resolving (best effort) {}", subartifacts); - try { - List subartifactResults = - toolboxResolver.resolveArtifacts(subartifacts); - batchSink.accept(subartifactResults.stream() - .map(ArtifactResult::getArtifact) - .collect(Collectors.toList())); - } catch (ArtifactResolutionException e) { - // ignore, this is "best effort" - batchSink.accept(e.getResults().stream() - .filter(ArtifactResult::isResolved) - .map(ArtifactResult::getArtifact) - .collect(Collectors.toList())); - } - } + .collect(Collectors.toList())); + } catch (ArtifactResolutionException e) { + // ignore, this is "best effort" + artifactSink.accept(e.getResults().stream() + .filter(ArtifactResult::isResolved) + .map(ArtifactResult::getArtifact) + .collect(Collectors.toList())); } } } - return !resolutionRoots.isEmpty(); } + return true; } @Override @@ -778,6 +792,36 @@ public boolean verify(RemoteRepository remoteRepository, String gav, String sha1 } } + // Various + + @Override + public boolean libYear( + ResolutionScope resolutionScope, + Collection resolutionRoots, + boolean quiet, + 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); + } + return !resolutionRoots.isEmpty(); + } + // Utils public static String humanReadableByteCountBin(long bytes) { diff --git a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxResolverImpl.java b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxResolverImpl.java index 7cc246a4..86df5fb2 100644 --- a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxResolverImpl.java +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxResolverImpl.java @@ -372,6 +372,31 @@ public Version findNewestVersion(Artifact artifact, boolean allowSnapshots) thro } } + public List findNewerVersions(Artifact artifact, boolean allowSnapshots) + throws VersionRangeResolutionException { + VersionRangeRequest rangeRequest = new VersionRangeRequest(); + rangeRequest.setArtifact(new DefaultArtifact( + artifact.getGroupId(), + artifact.getArtifactId(), + artifact.getClassifier(), + artifact.getExtension(), + artifact.getVersion().contains(",") ? artifact.getVersion() : "[" + artifact.getVersion() + ",)")); + rangeRequest.setRepositories(remoteRepositories); + rangeRequest.setRequestContext(CTX_TOOLBOX); + VersionRangeResult result = repositorySystem.resolveVersionRange(session, rangeRequest); + if (allowSnapshots) { + return result.getVersions(); + } else { + ArrayList versions = new ArrayList<>(result.getVersions().size()); + for (Version version : result.getVersions()) { + if (!version.toString().endsWith("SNAPSHOT")) { + versions.add(version); + } + } + return versions; + } + } + public List listAvailablePlugins(Collection groupIds) throws Exception { DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(this.session); session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS); diff --git a/shared/src/test/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImplTest.java b/shared/src/test/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImplTest.java new file mode 100644 index 00000000..c6677687 --- /dev/null +++ b/shared/src/test/java/eu/maveniverse/maven/toolbox/shared/internal/ToolboxCommandoImplTest.java @@ -0,0 +1,31 @@ +/* + * 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.internal; + +import eu.maveniverse.maven.mima.context.Context; +import eu.maveniverse.maven.mima.context.ContextOverrides; +import eu.maveniverse.maven.mima.context.Runtime; +import eu.maveniverse.maven.mima.context.Runtimes; +import eu.maveniverse.maven.toolbox.shared.NullOutput; +import java.io.IOException; +import java.nio.file.Paths; +import org.junit.jupiter.api.Test; + +public class ToolboxCommandoImplTest { + @Test + void search() throws IOException { + Runtime runtime = Runtimes.INSTANCE.getRuntime(); + try (Context context = runtime.create(ContextOverrides.create() + .withBasedirOverride(Paths.get("target").toAbsolutePath()) + .build())) { + ToolboxCommandoImpl tc = new ToolboxCommandoImpl(runtime, context); + + tc.search(ContextOverrides.CENTRAL, "junit:junit:4.13.2", new NullOutput()); + } + } +} diff --git a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/CLI.java b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/CLI.java index d8d68f07..6e819c10 100644 --- a/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/CLI.java +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/CLI.java @@ -28,6 +28,7 @@ GavExistsMojo.class, GavIdentifyMojo.class, GavInstallMojo.class, + GavLibYearMojo.class, GavListAvailablePluginsMojo.class, GavListMojo.class, GavListRepositoriesMojo.class, 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 new file mode 100644 index 00000000..921b6639 --- /dev/null +++ b/toolbox/src/main/java/eu/maveniverse/maven/toolbox/plugin/gav/GavLibYearMojo.java @@ -0,0 +1,56 @@ +/* + * 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.gav; + +import eu.maveniverse.maven.toolbox.plugin.GavSearchMojoSupport; +import eu.maveniverse.maven.toolbox.shared.Output; +import eu.maveniverse.maven.toolbox.shared.ResolutionScope; +import eu.maveniverse.maven.toolbox.shared.ToolboxCommando; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import picocli.CommandLine; + +/** + * Calculates "libyear" for Maven Artifacts transitively. + */ +@CommandLine.Command(name = "libyear", description = "Calculates libyear for artifacts.") +@Mojo(name = "gav-libyear", requiresProject = false, threadSafe = true) +public class GavLibYearMojo extends GavSearchMojoSupport { + /** + * The comma separated GAVs to "libyear". + */ + @CommandLine.Parameters(index = "0", description = "The comma separated GAVs to resolve", arity = "1") + @Parameter(property = "gav", required = true) + private String gav; + + /** + * Resolution scope to resolve (default 'runtime'). + */ + @CommandLine.Option( + names = {"--scope"}, + defaultValue = "runtime", + description = "Resolution scope to resolve (default 'runtime')") + @Parameter(property = "scope", defaultValue = "runtime", required = true) + private String scope; + + /** + * Comma separated list of BOMs to apply. + */ + @CommandLine.Option( + names = {"--boms"}, + defaultValue = "", + description = "Comma separated list of BOMs to apply") + @Parameter(property = "boms") + private String boms; + + @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); + } +}