Skip to content

Commit

Permalink
Libyear command (#33)
Browse files Browse the repository at this point in the history
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)]$ 
```
  • Loading branch information
cstamas authored May 15, 2024
1 parent 2c3d669 commit 3c7ad59
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResolutionRoot> resolutionRoots,
boolean quiet,
boolean allowSnapshots,
Output output)
throws Exception;
}
Original file line number Diff line number Diff line change
@@ -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 <a href="https://libyear.com/">libyear</a>
*/
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<Artifact, List<Version>, 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<Artifact, List<Version>, Version> versionSelector;
private final ConcurrentMap<Artifact, LibYear> libYear;

private LibYearSink(
Output output,
Context context,
ToolboxResolverImpl toolboxResolver,
ToolboxSearchApiImpl toolboxSearchApi,
boolean quiet,
boolean allowSnapshots,
BiFunction<Artifact, List<Version>, 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<Artifact, LibYear> 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<String> timedOnes = new TreeSet<>(Collections.reverseOrder());
TreeSet<String> outdated = new TreeSet<>();
if (!quiet) {
for (Map.Entry<Artifact, LibYear> 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;
}
}
Loading

0 comments on commit 3c7ad59

Please sign in to comment.