diff --git a/cli/pom.xml b/cli/pom.xml deleted file mode 100644 index 216737b3..00000000 --- a/cli/pom.xml +++ /dev/null @@ -1,196 +0,0 @@ - - - - 4.0.0 - - - eu.maveniverse.maven.mima - mima - 2.4.22-SNAPSHOT - - - cli - ${project.groupId}:${project.artifactId} - - - 11 - eu.maveniverse.maven.mima.cli.Main - eu.maveniverse.maven.mima.cli - - - - - - eu.maveniverse.maven.mima - context - - - org.slf4j - slf4j-api - - - info.picocli - picocli - - - info.picocli - picocli-shell-jline3 - - - org.fusesource.jansi - jansi - - - org.apache.maven.indexer - search-api - - - org.apache.maven.indexer - search-backend-remoterepository - - - org.apache.maven.indexer - search-backend-smo - - - - - org.apache.maven - maven-settings - ${version.maven} - provided - - - org.apache.maven.resolver - maven-resolver-api - provided - - - org.apache.maven.resolver - maven-resolver-util - provided - - - - - org.slf4j - slf4j-simple - runtime - - - org.slf4j - jcl-over-slf4j - runtime - - - - eu.maveniverse.maven.mima.runtime - standalone-static-uber - runtime - - - * - * - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - default-proc - - compile - - generate-sources - - - - info.picocli - picocli-codegen - ${version.picocli} - - - - -Aproject=${project.groupId}/${project.artifactId} - - only - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - ${mainClass} - - - - - - org.apache.maven.plugins - maven-shade-plugin - - - cli - - shade - - package - - false - true - uber - - - - - - ${mainClass} - ${project.artifactId} - ${project.version} - ${project.artifactId} - ${project.version} - ${project.groupId} - - - - - - *:* - - META-INF/MANIFEST.MF - META-INF/LICENSE - META-INF/DEPENDENCIES - META-INF/NOTICE - **/module-info.class - - - - - eu.maveniverse.maven.mima.cli.Main - - true - - - - - - - diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/ArtifactRecorder.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/ArtifactRecorder.java deleted file mode 100644 index aae6d43d..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/ArtifactRecorder.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.mima.cli; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import org.eclipse.aether.AbstractRepositoryListener; -import org.eclipse.aether.RepositoryEvent; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.repository.RemoteRepository; - -/** - * Records in-memory all the resolved artifacts. - */ -final class ArtifactRecorder extends AbstractRepositoryListener { - private final RemoteRepository sentinel = new RemoteRepository.Builder("none", "default", "fake").build(); - private final ConcurrentHashMap> artifactsMap = new ConcurrentHashMap<>(); - - @Override - public void artifactResolved(RepositoryEvent event) { - if (event.getException() == null) { - RemoteRepository repository = event.getRepository() instanceof RemoteRepository - ? (RemoteRepository) event.getRepository() - : sentinel; - artifactsMap.computeIfAbsent(repository, k -> new ArrayList<>()).add(event.getArtifact()); - } - } - - public RemoteRepository getSentinel() { - return sentinel; - } - - public Map> getArtifactsMap() { - return artifactsMap; - } - - public List getAllArtifacts() { - return artifactsMap.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); - } - - public Set getUniqueArtifacts() { - return new HashSet<>(getAllArtifacts()); - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Classpath.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Classpath.java deleted file mode 100644 index cc1d4ac5..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Classpath.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import java.io.File; -import java.util.stream.Collectors; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.collection.CollectRequest; -import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.resolution.DependencyRequest; -import org.eclipse.aether.resolution.DependencyResult; -import org.eclipse.aether.util.artifact.JavaScopes; -import org.eclipse.aether.util.filter.DependencyFilterUtils; -import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; -import picocli.CommandLine; - -/** - * Resolves transitively a given GAV and outputs classpath path. - */ -@CommandLine.Command(name = "classpath", description = "Resolves Maven Artifact and prints out the classpath") -public final class Classpath extends ResolverCommandSupport { - - enum ClasspathScope { - runtime, - compile, - test; - } - - @CommandLine.Parameters(index = "0", description = "The GAV to print classpath for") - private String gav; - - @CommandLine.Option(names = "--scope", defaultValue = "runtime") - private ClasspathScope scope; - - @CommandLine.Option( - names = {"--boms"}, - defaultValue = "", - split = ",", - description = "Comma separated list of BOMs to apply") - private String[] boms; - - @Override - protected Integer doCall(Context context) throws Exception { - java.util.List managedDependencies = importBoms(context, boms); - Artifact artifact = parseGav(gav, managedDependencies); - - CollectRequest collectRequest = new CollectRequest(); - collectRequest.setRoot(new Dependency(artifact, JavaScopes.COMPILE)); - collectRequest.setRepositories(context.remoteRepositories()); - collectRequest.setManagedDependencies(managedDependencies); - DependencyRequest dependencyRequest = - new DependencyRequest(collectRequest, DependencyFilterUtils.classpathFilter(scope.name())); - - verbose("Resolving {}", dependencyRequest); - DependencyResult dependencyResult = - context.repositorySystem().resolveDependencies(getRepositorySystemSession(), dependencyRequest); - - PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); - dependencyResult.getRoot().accept(nlg); - // TODO: Do not use PreorderNodeListGenerator#getClassPath() until MRESOLVER-483 is fixed/released - info("{}", nlg.getFiles().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator))); - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/CommandSupport.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/CommandSupport.java deleted file mode 100644 index d02c1785..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/CommandSupport.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import eu.maveniverse.maven.mima.context.ContextOverrides; -import eu.maveniverse.maven.mima.context.HTTPProxy; -import eu.maveniverse.maven.mima.context.MavenSystemHome; -import eu.maveniverse.maven.mima.context.MavenUserHome; -import eu.maveniverse.maven.mima.context.Runtime; -import eu.maveniverse.maven.mima.context.Runtimes; -import java.io.PrintStream; -import java.nio.file.Path; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -import org.apache.maven.settings.Proxy; -import org.apache.maven.settings.Settings; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.util.version.GenericVersionScheme; -import org.eclipse.aether.version.VersionScheme; -import org.slf4j.helpers.MessageFormatter; -import picocli.CommandLine; - -/** - * Support class. - */ -public abstract class CommandSupport implements Callable { - @CommandLine.Option( - names = {"-v", "--verbose"}, - description = "Be verbose about things happening") - protected boolean verbose; - - @CommandLine.Option( - names = {"-o", "--offline"}, - description = "Work offline") - protected boolean offline; - - @CommandLine.Option( - names = {"-s", "--settings"}, - description = "The Maven User Settings file to use") - protected Path userSettingsXml; - - @CommandLine.Option( - names = {"-gs", "--global-settings"}, - description = "The Maven Global Settings file to use") - protected Path globalSettingsXml; - - @CommandLine.Option( - names = {"-P", "--activate-profiles"}, - split = ",", - description = "Comma delimited list of profile IDs to activate (may use '+', '-' and '!' prefix)") - protected java.util.List profiles; - - @CommandLine.Option( - names = {"-D", "--define"}, - description = "Define a user property") - protected List userProperties; - - @CommandLine.Option( - names = {"--proxy"}, - description = "Define a HTTP proxy (host:port)") - protected String proxy; - - private static final ConcurrentHashMap> EXECUTION_CONTEXT = new ConcurrentHashMap<>(); - - protected Object getOrCreate(String key, Supplier supplier) { - ArrayDeque deque = EXECUTION_CONTEXT.computeIfAbsent(key, k -> new ArrayDeque<>()); - if (deque.isEmpty()) { - deque.push(supplier.get()); - } - return deque.peek(); - } - - protected void push(String key, Object object) { - ArrayDeque deque = EXECUTION_CONTEXT.computeIfAbsent(key, k -> new ArrayDeque<>()); - deque.push(object); - } - - protected Object pop(String key) { - ArrayDeque deque = EXECUTION_CONTEXT.get(key); - if (deque == null || deque.isEmpty()) { - throw new IllegalStateException("No element to pop"); - } - return deque.pop(); - } - - protected Object peek(String key) { - ArrayDeque deque = EXECUTION_CONTEXT.get(key); - if (deque == null || deque.isEmpty()) { - throw new IllegalStateException("No element to peek"); - } - return deque.peek(); - } - - protected void mayDumpEnv(Runtime runtime, Context context, boolean verbose) { - info("MIMA (Runtime '{}' version {})", runtime.name(), runtime.version()); - info("===="); - info(" Maven version {}", runtime.mavenVersion()); - info(" Managed {}", runtime.managedRepositorySystem()); - info(" Basedir {}", context.basedir()); - info(" Offline {}", context.repositorySystemSession().isOffline()); - - MavenSystemHome mavenSystemHome = context.mavenSystemHome(); - info(""); - info(" MAVEN_HOME {}", mavenSystemHome == null ? "undefined" : mavenSystemHome.basedir()); - if (mavenSystemHome != null) { - info(" settings.xml {}", mavenSystemHome.settingsXml()); - info(" toolchains.xml {}", mavenSystemHome.toolchainsXml()); - } - - MavenUserHome mavenUserHome = context.mavenUserHome(); - info(""); - info(" USER_HOME {}", mavenUserHome.basedir()); - info(" settings.xml {}", mavenUserHome.settingsXml()); - info(" settings-security.xml {}", mavenUserHome.settingsSecurityXml()); - info(" local repository {}", mavenUserHome.localRepository()); - - info(""); - info(" PROFILES"); - info(" Active {}", context.contextOverrides().getActiveProfileIds()); - info(" Inactive {}", context.contextOverrides().getInactiveProfileIds()); - - info(""); - info(" REMOTE REPOSITORIES"); - for (RemoteRepository repository : context.remoteRepositories()) { - if (repository.getMirroredRepositories().isEmpty()) { - info(" {}", repository); - } else { - info(" {}, mirror of", repository); - for (RemoteRepository mirrored : repository.getMirroredRepositories()) { - info(" {}", mirrored); - } - } - } - - if (context.httpProxy() != null) { - HTTPProxy proxy = context.httpProxy(); - info(""); - info(" HTTP PROXY"); - info(" url {}://{}:{}", proxy.getProtocol(), proxy.getHost(), proxy.getPort()); - info(" nonProxyHosts {}", proxy.getNonProxyHosts()); - } - - if (verbose) { - info(""); - info(" USER PROPERTIES"); - context.contextOverrides().getUserProperties().entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .forEach(e -> info(" {}={}", e.getKey(), e.getValue())); - info(" SYSTEM PROPERTIES"); - context.contextOverrides().getSystemProperties().entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .forEach(e -> info(" {}={}", e.getKey(), e.getValue())); - info(" CONFIG PROPERTIES"); - context.contextOverrides().getConfigProperties().entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .forEach(e -> info(" {}={}", e.getKey(), e.getValue())); - } - info(""); - } - - protected Runtime getRuntime() { - return (Runtime) getOrCreate(Runtime.class.getName(), Runtimes.INSTANCE::getRuntime); - } - - protected ContextOverrides getContextOverrides() { - return (ContextOverrides) getOrCreate(ContextOverrides.class.getName(), () -> { - // create builder with some sane defaults - ContextOverrides.Builder builder = ContextOverrides.create().withUserSettings(true); - if (offline) { - builder.offline(true); - } - if (userSettingsXml != null) { - builder.withUserSettingsXmlOverride(userSettingsXml); - } - if (globalSettingsXml != null) { - builder.withGlobalSettingsXmlOverride(globalSettingsXml); - } - if (profiles != null && !profiles.isEmpty()) { - ArrayList activeProfiles = new ArrayList<>(); - ArrayList inactiveProfiles = new ArrayList<>(); - for (String profile : profiles) { - if (profile.startsWith("+")) { - activeProfiles.add(profile.substring(1)); - } else if (profile.startsWith("-") || profile.startsWith("!")) { - inactiveProfiles.add(profile.substring(1)); - } else { - activeProfiles.add(profile); - } - } - builder.withActiveProfileIds(activeProfiles).withInactiveProfileIds(inactiveProfiles); - } - if (userProperties != null && !userProperties.isEmpty()) { - HashMap defined = new HashMap<>(userProperties.size()); - String name; - String value; - for (String property : userProperties) { - int i = property.indexOf('='); - if (i <= 0) { - name = property.trim(); - value = Boolean.TRUE.toString(); - } else { - name = property.substring(0, i).trim(); - value = property.substring(i + 1); - } - defined.put(name, value); - } - builder.userProperties(defined); - } - if (proxy != null) { - String[] elems = proxy.split(":"); - if (elems.length != 2) { - throw new IllegalArgumentException("Proxy must be specified as 'host:port'"); - } - Proxy proxySettings = new Proxy(); - proxySettings.setId("mima-mixin"); - proxySettings.setActive(true); - proxySettings.setProtocol("http"); - proxySettings.setHost(elems[0]); - proxySettings.setPort(Integer.parseInt(elems[1])); - Settings proxyMixin = new Settings(); - proxyMixin.addProxy(proxySettings); - builder.withEffectiveSettingsMixin(proxyMixin); - } - return builder.build(); - }); - } - - protected Context getContext() { - return (Context) getOrCreate(Context.class.getName(), () -> getRuntime().create(getContextOverrides())); - } - - protected VersionScheme getVersionScheme() { - return new GenericVersionScheme(); - } - - protected void verbose(String message) { - log(true, System.out, message); - } - - protected void verbose(String format, Object arg1) { - log(true, System.out, MessageFormatter.format(format, arg1).getMessage()); - } - - protected void verbose(String format, Object arg1, Object arg2) { - log(true, System.out, MessageFormatter.format(format, arg1, arg2).getMessage()); - } - - protected void verbose(String format, Object arg1, Object arg2, Object arg3) { - log( - true, - System.out, - MessageFormatter.arrayFormat(format, new Object[] {arg1, arg2, arg3}) - .getMessage()); - } - - protected void info(String message) { - log(false, System.out, message); - } - - protected void info(String format, Object arg1) { - log(false, System.out, MessageFormatter.format(format, arg1).getMessage()); - } - - protected void info(String format, Object arg1, Object arg2) { - log(false, System.out, MessageFormatter.format(format, arg1, arg2).getMessage()); - } - - protected void info(String format, Object arg1, Object arg2, Object arg3) { - log( - false, - System.out, - MessageFormatter.arrayFormat(format, new Object[] {arg1, arg2, arg3}) - .getMessage()); - } - - protected void error(String message, Throwable throwable) { - log(System.err, failure(message), throwable); - } - - private void log(boolean verbose, PrintStream ps, String message) { - if (verbose && !this.verbose) { - return; - } - log(ps, message, null); - } - - private void log(PrintStream ps, String message, Throwable throwable) { - ps.println(message); - writeThrowable(throwable, ps); - } - - private static String failure(String format) { - return "\u001b[1;31m" + format + "\u001b[m"; - } - - private static String strong(String format) { - return "\u001b[1m" + format + "\u001b[m"; - } - - private void writeThrowable(Throwable t, PrintStream stream) { - if (t == null) { - return; - } - String builder = failure(t.getClass().getName()); - if (t.getMessage() != null) { - builder += ": " + failure(t.getMessage()); - } - stream.println(builder); - - printStackTrace(t, stream, ""); - } - - private void printStackTrace(Throwable t, PrintStream stream, String prefix) { - StringBuilder builder = new StringBuilder(); - for (StackTraceElement e : t.getStackTrace()) { - builder.append(prefix); - builder.append(" "); - builder.append(strong("at")); - builder.append(" "); - builder.append(e.getClassName()); - builder.append("."); - builder.append(e.getMethodName()); - builder.append(" ("); - builder.append(strong(getLocation(e))); - builder.append(")"); - stream.println(builder); - builder.setLength(0); - } - for (Throwable se : t.getSuppressed()) { - writeThrowable(se, stream, "Suppressed", prefix + " "); - } - Throwable cause = t.getCause(); - if (cause != null && t != cause) { - writeThrowable(cause, stream, "Caused by", prefix); - } - } - - private void writeThrowable(Throwable t, PrintStream stream, String caption, String prefix) { - StringBuilder builder = new StringBuilder(); - builder.append(prefix) - .append(strong(caption)) - .append(": ") - .append(t.getClass().getName()); - if (t.getMessage() != null) { - builder.append(": ").append(failure(t.getMessage())); - } - stream.println(builder); - - printStackTrace(t, stream, prefix); - } - - protected String getLocation(final StackTraceElement e) { - assert e != null; - - if (e.isNativeMethod()) { - return "Native Method"; - } else if (e.getFileName() == null) { - return "Unknown Source"; - } else if (e.getLineNumber() >= 0) { - return e.getFileName() + ":" + e.getLineNumber(); - } else { - return e.getFileName(); - } - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Deploy.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Deploy.java deleted file mode 100644 index 0e8cd118..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Deploy.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import java.nio.file.Path; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.deployment.DeployRequest; -import org.eclipse.aether.deployment.DeploymentException; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.util.artifact.SubArtifact; -import picocli.CommandLine; - -/** - * Deploys an artifact into remote repository. - */ -@CommandLine.Command(name = "deploy", description = "Deploys Maven Artifacts") -public final class Deploy extends ResolverCommandSupport { - - @CommandLine.Parameters(index = "0", description = "The GAV to deploy") - private String gav; - - @CommandLine.Parameters(index = "1", description = "The artifact JAR file") - private Path jar; - - @CommandLine.Parameters(index = "2", description = "The artifact POM file") - private Path pom; - - @CommandLine.Parameters(index = "3", description = "The RemoteRepository spec (id::url)") - private String remoteRepositorySpec; - - @Override - protected Integer doCall(Context context) throws DeploymentException { - Artifact jarArtifact = new DefaultArtifact(gav); - jarArtifact = jarArtifact.setFile(jar.toFile()); - - Artifact pomArtifact = new SubArtifact(jarArtifact, "", "pom"); - pomArtifact = pomArtifact.setFile(pom.toFile()); - - RemoteRepository remoteRepository = getContext() - .repositorySystem() - .newDeploymentRepository( - getRepositorySystemSession(), buildRemoteRepositoryFromSpec(remoteRepositorySpec)); - - DeployRequest deployRequest = new DeployRequest(); - deployRequest.addArtifact(jarArtifact).addArtifact(pomArtifact).setRepository(remoteRepository); - - verbose("Deploying {}", deployRequest); - context.repositorySystem().deploy(getRepositorySystemSession(), deployRequest); - - info("Deployed {}", gav); - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/DeployRecorded.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/DeployRecorded.java deleted file mode 100644 index cdf251da..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/DeployRecorded.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import java.util.Set; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.deployment.DeployRequest; -import org.eclipse.aether.deployment.DeploymentException; -import org.eclipse.aether.repository.RemoteRepository; -import picocli.CommandLine; - -/** - * Deploys recorded artifacts to remote repository. - */ -@CommandLine.Command(name = "deployRecorded", description = "Deploys recorded Maven Artifacts") -public final class DeployRecorded extends ResolverCommandSupport { - @CommandLine.Parameters(index = "0", description = "The RemoteRepository spec (id::url)") - private String remoteRepositorySpec; - - @Override - protected Integer doCall(Context context) throws DeploymentException { - info("Deploying recorded"); - - ArtifactRecorder recorder = (ArtifactRecorder) pop(ArtifactRecorder.class.getName()); - DeployRequest deployRequest = new DeployRequest(); - RemoteRepository remoteRepository = getContext() - .repositorySystem() - .newDeploymentRepository( - getRepositorySystemSession(), buildRemoteRepositoryFromSpec(remoteRepositorySpec)); - deployRequest.setRepository(remoteRepository); - Set uniqueArtifacts = recorder.getUniqueArtifacts(); - uniqueArtifacts.forEach(deployRequest::addArtifact); - - context.repositorySystem().deploy(getRepositorySystemSession(), deployRequest); - - info(""); - info("Deployed recorded {} artifacts to {}", uniqueArtifacts.size(), remoteRepository); - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Dump.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Dump.java deleted file mode 100644 index f65d5463..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Dump.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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.mima.cli; - -import picocli.CommandLine; - -/** - * Dumps MIMA environment. - */ -@CommandLine.Command(name = "dump", description = "Dump MIMA environment") -public final class Dump extends CommandSupport { - @Override - public Integer call() { - mayDumpEnv(getRuntime(), getContext(), true); - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Exists.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Exists.java deleted file mode 100644 index 1761b32a..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Exists.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.mima.cli; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import org.apache.maven.search.api.SearchBackend; -import org.apache.maven.search.api.SearchRequest; -import org.apache.maven.search.api.SearchResponse; -import org.apache.maven.search.api.request.Query; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.util.artifact.SubArtifact; -import picocli.CommandLine; - -/** - * Checks given GAV for existence in a remote repository. - */ -@CommandLine.Command(name = "exists", description = "Checks Maven Artifact existence") -public final class Exists extends SearchCommandSupport { - - @CommandLine.Parameters(description = "The GAVs to check") - private List gavs; - - @CommandLine.Option( - names = {"--pom"}, - description = "Check POM presence as well (derive coordinates from GAV)") - private boolean pom; - - @CommandLine.Option( - names = {"--sources"}, - description = "Check sources JARs as well (derive coordinates from GAV)") - private boolean sources; - - @CommandLine.Option( - names = {"--javadoc"}, - description = "Check javadoc JARs as well (derive coordinates from GAV)") - private boolean javadoc; - - @CommandLine.Option( - names = {"--all-required"}, - description = - "If set, any missing derived artifact will be reported as failure as well (otherwise just the specified GAVs presence is required)") - private boolean allRequired; - - @Override - protected Integer doCall() throws IOException { - ArrayList missingOnes = new ArrayList<>(); - ArrayList existingOnes = new ArrayList<>(); - try (SearchBackend backend = getRemoteRepositoryBackend(repositoryId, repositoryBaseUri, repositoryVendor)) { - for (String gav : gavs) { - Artifact artifact = new DefaultArtifact(gav); - boolean exists = exists(backend, artifact); - if (!exists) { - missingOnes.add(artifact); - } else { - existingOnes.add(artifact); - } - info("Artifact {} {}", artifact, exists ? "EXISTS" : "NOT EXISTS"); - if (pom && !"pom".equals(artifact.getExtension())) { - Artifact pom = new SubArtifact(artifact, null, "pom"); - exists = exists(backend, pom); - if (!exists && allRequired) { - missingOnes.add(pom); - } else if (allRequired) { - existingOnes.add(pom); - } - info(" {} {}", pom, exists ? "EXISTS" : "NOT EXISTS"); - } - if (sources) { - Artifact sources = new SubArtifact(artifact, "sources", "jar"); - exists = exists(backend, sources); - if (!exists && allRequired) { - missingOnes.add(sources); - } else if (allRequired) { - existingOnes.add(sources); - } - info(" {} {}", sources, exists ? "EXISTS" : "NOT EXISTS"); - } - if (javadoc) { - Artifact javadoc = new SubArtifact(artifact, "javadoc", "jar"); - exists = exists(backend, javadoc); - if (!exists && allRequired) { - missingOnes.add(javadoc); - } else if (allRequired) { - existingOnes.add(javadoc); - } - info(" {} {}", javadoc, exists ? "EXISTS" : "NOT EXISTS"); - } - } - } - info(""); - info( - "Checked TOTAL of {} (existing: {} not existing: {})", - existingOnes.size() + missingOnes.size(), - existingOnes.size(), - missingOnes.size()); - return missingOnes.isEmpty() ? 0 : 1; - } - - private boolean exists(SearchBackend backend, Artifact artifact) throws IOException { - Query query = toRrQuery(artifact); - SearchRequest searchRequest = new SearchRequest(query); - SearchResponse searchResponse = backend.search(searchRequest); - return searchResponse.getTotalHits() == 1; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Graph.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Graph.java deleted file mode 100644 index 4a550dda..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Graph.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.collection.CollectRequest; -import org.eclipse.aether.collection.DependencyManager; -import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.util.artifact.JavaScopes; -import org.eclipse.aether.util.graph.manager.ClassicDependencyManager; -import org.eclipse.aether.util.graph.manager.DefaultDependencyManager; -import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; -import org.eclipse.aether.util.graph.manager.NoopDependencyManager; -import org.eclipse.aether.util.graph.manager.TransitiveDependencyManager; -import org.eclipse.aether.util.graph.selector.AndDependencySelector; -import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector; -import org.eclipse.aether.util.graph.selector.OptionalDependencySelector; -import org.eclipse.aether.util.graph.selector.ScopeDependencySelector; -import org.eclipse.aether.util.graph.transformer.ChainedDependencyGraphTransformer; -import org.eclipse.aether.util.graph.transformer.ConflictResolver; -import org.eclipse.aether.util.graph.transformer.JavaDependencyContextRefiner; -import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver; -import org.eclipse.aether.util.graph.transformer.JavaScopeSelector; -import org.eclipse.aether.util.graph.transformer.NearestVersionSelector; -import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector; -import org.eclipse.aether.util.graph.traverser.FatArtifactTraverser; -import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper; -import picocli.CommandLine; - -/** - * Collects given GAV and output its dependency graph. - */ -@CommandLine.Command(name = "graph", description = "Displays dependency graph") -public final class Graph extends ResolverCommandSupport { - - @CommandLine.Parameters(index = "0", description = "The GAV to graph") - private String gav; - - @CommandLine.Option( - names = {"--dependencyManager"}, - defaultValue = "classic", - description = "Dependency manager to use (classic, default, noop, transitive)") - private String dependencyManager; - - @CommandLine.Option( - names = {"--excludeScopes"}, - defaultValue = JavaScopes.TEST, - split = ",", - description = "Scopes to exclude (default is 'test')") - private String[] excludeScopes; - - @CommandLine.Option( - names = {"--boms"}, - defaultValue = "", - split = ",", - description = "Comma separated list of BOMs to apply") - private String[] boms; - - @Override - protected Integer doCall(Context context) throws Exception { - DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(context.repositorySystemSession()); - RepositorySystem repositorySystem = context.repositorySystem(); - - session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, ConflictResolver.Verbosity.FULL); - session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); - - session.setDependencyManager(dependencyManager()); - - session.setDependencySelector(new AndDependencySelector( - new ScopeDependencySelector(excludeScopes), - new OptionalDependencySelector(), - new ExclusionDependencySelector())); - session.setDependencyTraverser(new FatArtifactTraverser()); - - session.setDependencyGraphTransformer(new ChainedDependencyGraphTransformer( - new ConflictResolver( - new NearestVersionSelector(), new JavaScopeSelector(), - new SimpleOptionalitySelector(), new JavaScopeDeriver()), - new JavaDependencyContextRefiner())); - - java.util.List managedDependencies = importBoms(context, boms); - Artifact artifact = parseGav(gav, managedDependencies); - - CollectRequest collectRequest = new CollectRequest(); - collectRequest.setRoot(new Dependency(artifact, "")); - collectRequest.setRepositories(context.remoteRepositories()); - collectRequest.setManagedDependencies(managedDependencies); - - verbose("Collecting {}", collectRequest); - repositorySystem - .collectDependencies(session, collectRequest) - .getRoot() - .accept(new DependencyGraphDumper(this::info)); - return 0; - } - - private DependencyManager dependencyManager() { - if ("classic".equalsIgnoreCase(dependencyManager)) { - return new ClassicDependencyManager(); - } else if ("default".equalsIgnoreCase(dependencyManager)) { - return new DefaultDependencyManager(); - } else if ("noop".equalsIgnoreCase(dependencyManager)) { - return new NoopDependencyManager(); - } else if ("transitive".equalsIgnoreCase(dependencyManager)) { - return new TransitiveDependencyManager(); - } else { - throw new IllegalArgumentException("Unknown dependency manager: " + dependencyManager); - } - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Identify.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Identify.java deleted file mode 100644 index 408dd902..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Identify.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.mima.cli; - -import static org.apache.maven.search.api.request.FieldQuery.fieldQuery; - -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import org.apache.maven.search.api.MAVEN; -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.util.ChecksumUtils; -import picocli.CommandLine; - -/** - * Identify artifact, either by provided SHA-1 or calculated SHA-1 of a file pointed at. - */ -@CommandLine.Command(name = "identify", description = "Identifies Maven Artifacts") -public final class Identify extends SearchCommandSupport { - - @CommandLine.Parameters(index = "0", description = "File or sha1 checksum to identify artifact with") - private String target; - - @Override - protected Integer doCall() throws IOException { - String sha1; - if (Files.exists(Paths.get(target))) { - try { - verbose("Calculating SHA1 of file {}", target); - MessageDigest sha1md = MessageDigest.getInstance("SHA-1"); - byte[] buf = new byte[8192]; - int read; - try (FileInputStream fis = new FileInputStream(target)) { - read = fis.read(buf); - while (read != -1) { - sha1md.update(buf, 0, read); - read = fis.read(buf); - } - } - sha1 = ChecksumUtils.toHexString(sha1md.digest()); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA1 MessageDigest unavailable", e); - } - } else { - sha1 = target; - } - verbose("Identifying artifact with SHA1={}", sha1); - try (SearchBackend backend = getSmoBackend(repositoryId)) { - SearchRequest searchRequest = new SearchRequest(fieldQuery(MAVEN.SHA1, sha1)); - SearchResponse searchResponse = backend.search(searchRequest); - - renderPage(searchResponse.getPage(), null).forEach(this::info); - while (searchResponse.getCurrentHits() > 0) { - searchResponse = - backend.search(searchResponse.getSearchRequest().nextPage()); - renderPage(searchResponse.getPage(), null).forEach(this::info); - } - } - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Install.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Install.java deleted file mode 100644 index f5ed202d..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Install.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import java.nio.file.Path; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.installation.InstallRequest; -import org.eclipse.aether.installation.InstallationException; -import org.eclipse.aether.util.artifact.SubArtifact; -import picocli.CommandLine; - -/** - * Installs an artifact into local repository. - */ -@CommandLine.Command(name = "install", description = "Installs Maven Artifacts") -public final class Install extends ResolverCommandSupport { - - @CommandLine.Parameters(index = "0", description = "The GAV to install") - private String gav; - - @CommandLine.Parameters(index = "1", description = "The artifact JAR file") - private Path jar; - - @CommandLine.Parameters(index = "2", description = "The artifact POM file") - private Path pom; - - @Override - protected Integer doCall(Context context) throws InstallationException { - Artifact jarArtifact = new DefaultArtifact(gav); - jarArtifact = jarArtifact.setFile(jar.toFile()); - - Artifact pomArtifact = new SubArtifact(jarArtifact, "", "pom"); - pomArtifact = pomArtifact.setFile(pom.toFile()); - - InstallRequest installRequest = new InstallRequest(); - installRequest.addArtifact(jarArtifact).addArtifact(pomArtifact); - - context.repositorySystem().install(getRepositorySystemSession(), installRequest); - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/List.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/List.java deleted file mode 100644 index 0115d9e4..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/List.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.mima.cli; - -import static org.apache.maven.search.api.request.BooleanQuery.and; -import static org.apache.maven.search.api.request.FieldQuery.fieldQuery; - -import java.io.IOException; -import java.util.function.Predicate; -import org.apache.maven.search.api.MAVEN; -import org.apache.maven.search.api.SearchBackend; -import org.apache.maven.search.api.SearchRequest; -import org.apache.maven.search.api.SearchResponse; -import org.apache.maven.search.api.request.Query; -import org.eclipse.aether.version.InvalidVersionSpecificationException; -import org.eclipse.aether.version.VersionConstraint; -import org.eclipse.aether.version.VersionScheme; -import picocli.CommandLine; - -/** - * Lists remote repository by given "gavoid" (G or G:A or G:A:V where V may be version constraint). - */ -@CommandLine.Command(name = "list", description = "Lists Maven Artifacts") -public final class List extends SearchCommandSupport { - - @CommandLine.Parameters(index = "0", description = "The GAV-oid to list (G or G:A or G:A:V)") - private String gavoid; - - @Override - protected Integer doCall() throws IOException { - try (SearchBackend backend = getRemoteRepositoryBackend(repositoryId, repositoryBaseUri, repositoryVendor)) { - String[] elements = gavoid.split(":"); - if (elements.length < 1 || elements.length > 3) { - throw new IllegalArgumentException("Invalid gavoid"); - } - - Query query = fieldQuery(MAVEN.GROUP_ID, elements[0]); - if (elements.length > 1) { - query = and(query, fieldQuery(MAVEN.ARTIFACT_ID, elements[1])); - } - - VersionScheme versionScheme = getVersionScheme(); - Predicate versionPredicate = null; - if (elements.length > 2) { - try { - VersionConstraint versionConstraint = versionScheme.parseVersionConstraint(elements[2]); - if (versionConstraint.getRange() != null) { - versionPredicate = s -> { - try { - return versionConstraint.containsVersion(versionScheme.parseVersion(s)); - } catch (InvalidVersionSpecificationException e) { - return false; - } - }; - } - } catch (InvalidVersionSpecificationException e) { - // ignore and continue as before - } - if (versionPredicate == null) { - query = and(query, fieldQuery(MAVEN.VERSION, elements[2])); - } - } - SearchRequest searchRequest = new SearchRequest(query); - SearchResponse searchResponse = backend.search(searchRequest); - - renderPage(searchResponse.getPage(), versionPredicate).forEach(this::info); - } - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Main.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Main.java deleted file mode 100644 index 7a3a002d..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Main.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import picocli.CommandLine; - -/** - * Main. - */ -@CommandLine.Command( - name = "mima", - subcommands = { - Classpath.class, - Deploy.class, - DeployRecorded.class, - Dump.class, - Exists.class, - Graph.class, - Identify.class, - Install.class, - List.class, - Search.class, - Record.class, - Repl.class, - Resolve.class, - Verify.class - }, - versionProvider = Main.class, - description = "MIMA CLI", - mixinStandardHelpOptions = true) -public class Main extends CommandSupport implements CommandLine.IVersionProvider { - @Override - public String[] getVersion() { - return new String[] {"MIMA " + getRuntime().version()}; - } - - @Override - public Integer call() { - try (Context context = getContext()) { - mayDumpEnv(getRuntime(), context, false); - new Repl().call(); - } - return 0; - } - - public static void main(String... args) { - System.exit(new CommandLine(new Main()).execute(args)); - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Record.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Record.java deleted file mode 100644 index 16eda325..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Record.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.resolution.DependencyResolutionException; -import org.eclipse.aether.util.listener.ChainedRepositoryListener; -import picocli.CommandLine; - -/** - * Records resolved artifacts. - */ -@CommandLine.Command(name = "record", description = "Records resolved Maven Artifacts") -public final class Record extends ResolverCommandSupport { - - @Override - protected Integer doCall(Context context) throws DependencyResolutionException { - ArtifactRecorder recorder = new ArtifactRecorder(); - DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(getRepositorySystemSession()); - session.setRepositoryListener( - session.getRepositoryListener() != null - ? ChainedRepositoryListener.newInstance(session.getRepositoryListener(), recorder) - : recorder); - push(ArtifactRecorder.class.getName(), recorder); - push(RepositorySystemSession.class.getName(), session); - - info("Recording..."); - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Repl.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Repl.java deleted file mode 100644 index da81eac8..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Repl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import java.nio.file.Path; -import org.jline.builtins.ConfigurationPath; -import org.jline.console.SystemRegistry; -import org.jline.console.impl.Builtins; -import org.jline.console.impl.SystemRegistryImpl; -import org.jline.keymap.KeyMap; -import org.jline.reader.Binding; -import org.jline.reader.EndOfFileException; -import org.jline.reader.LineReader; -import org.jline.reader.LineReaderBuilder; -import org.jline.reader.MaskingCallback; -import org.jline.reader.Parser; -import org.jline.reader.Reference; -import org.jline.reader.UserInterruptException; -import org.jline.reader.impl.DefaultParser; -import org.jline.reader.impl.history.DefaultHistory; -import org.jline.terminal.Terminal; -import org.jline.terminal.TerminalBuilder; -import org.jline.terminal.impl.jansi.JansiTerminalProvider; -import org.jline.widget.TailTipWidgets; -import picocli.CommandLine; -import picocli.shell.jline3.PicocliCommands; - -@CommandLine.Command(name = "repl", description = "REPL console") -public class Repl extends CommandSupport { - @Override - public Integer call() { - Class tp = JansiTerminalProvider.class; - Context context = getContext(); - Context derivedContext = context.customize(context.contextOverrides()); - push(Context.class.getName(), derivedContext); - - // set up JLine built-in commands - ConfigurationPath configPath = new ConfigurationPath(context.basedir(), context.basedir()); - Builtins builtins = new Builtins(context::basedir, configPath, null); - builtins.rename(org.jline.console.impl.Builtins.Command.TTOP, "top"); - builtins.alias("zle", "widget"); - builtins.alias("bindkey", "keymap"); - // set up picocli commands - CommandLine cmd = new CommandLine(new Main()); - PicocliCommands picocliCommands = new PicocliCommands(cmd); - - Parser parser = new DefaultParser(); - - try (Terminal terminal = TerminalBuilder.builder().name("mima").build()) { - SystemRegistry systemRegistry = new SystemRegistryImpl(parser, terminal, context::basedir, configPath); - systemRegistry.setCommandRegistries(builtins, picocliCommands); - - Path history = context.mavenUserHome().basedir().resolve(".mima_history"); - LineReader reader = LineReaderBuilder.builder() - .terminal(terminal) - .history(new DefaultHistory()) - .completer(systemRegistry.completer()) - .parser(parser) - .variable(LineReader.LIST_MAX, 50) // max tab completion candidates - .variable(LineReader.HISTORY_FILE, history) - .build(); - builtins.setLineReader(reader); - new TailTipWidgets(reader, systemRegistry::commandDescription, 5, TailTipWidgets.TipType.COMPLETER); - KeyMap keyMap = reader.getKeyMaps().get("main"); - keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s")); - - String prompt = "prompt> "; - String rightPrompt = null; - - // start the shell and process input until the user quits with Ctrl-D - String line; - while (true) { - try { - systemRegistry.cleanUp(); - line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null); - systemRegistry.execute(line); - } catch (UserInterruptException e) { - // Ignore - } catch (EndOfFileException e) { - return 0; - } catch (Exception e) { - systemRegistry.trace(e); - return 2; - } - } - } catch (Exception e) { - error("REPL Failure: ", e); - return 1; - } finally { - context.close(); - } - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Resolve.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Resolve.java deleted file mode 100644 index c581f817..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Resolve.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.collection.CollectRequest; -import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.resolution.ArtifactRequest; -import org.eclipse.aether.resolution.ArtifactResolutionException; -import org.eclipse.aether.resolution.DependencyRequest; -import org.eclipse.aether.util.artifact.JavaScopes; -import org.eclipse.aether.util.artifact.SubArtifact; -import org.eclipse.aether.util.filter.DependencyFilterUtils; -import org.eclipse.aether.util.listener.ChainedRepositoryListener; -import picocli.CommandLine; - -/** - * Resolves transitively given artifact. - */ -@CommandLine.Command(name = "resolve", description = "Resolves Maven Artifacts") -public final class Resolve extends ResolverCommandSupport { - - @CommandLine.Parameters(index = "0", description = "The GAV to resolve") - private String gav; - - @CommandLine.Option( - names = {"--sources"}, - description = "Download sources JARs as well (best effort)") - private boolean sources; - - @CommandLine.Option( - names = {"--javadoc"}, - description = "Download javadoc JARs as well (best effort)") - private boolean javadoc; - - @CommandLine.Option( - names = {"--scope"}, - defaultValue = JavaScopes.COMPILE, - description = "Scope to resolve") - private String scope; - - @CommandLine.Option( - names = {"--boms"}, - defaultValue = "", - split = ",", - description = "Comma separated list of BOMs to apply") - private String[] boms; - - @Override - protected Integer doCall(Context context) throws Exception { - DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(getRepositorySystemSession()); - ArtifactRecorder recorder = new ArtifactRecorder(); - session.setRepositoryListener( - session.getRepositoryListener() != null - ? ChainedRepositoryListener.newInstance(session.getRepositoryListener(), recorder) - : recorder); - - java.util.List managedDependencies = importBoms(context, boms); - Artifact resolvedArtifact = parseGav(gav, managedDependencies); - - CollectRequest collectRequest = new CollectRequest(); - collectRequest.setRoot(new Dependency(resolvedArtifact, JavaScopes.COMPILE)); - collectRequest.setRepositories(context.remoteRepositories()); - collectRequest.setManagedDependencies(managedDependencies); - DependencyRequest dependencyRequest = - new DependencyRequest(collectRequest, DependencyFilterUtils.classpathFilter(scope)); - - info("Resolving {}", collectRequest.getRoot().getArtifact()); - context.repositorySystem().resolveDependencies(session, dependencyRequest); - - ArrayList artifactRequests = new ArrayList<>(); - for (Map.Entry> entry : - recorder.getArtifactsMap().entrySet()) { - List repositories = - entry.getKey() == recorder.getSentinel() ? null : Collections.singletonList(entry.getKey()); - for (Artifact artifact : entry.getValue()) { - if ("jar".equals(artifact.getExtension()) && "".equals(artifact.getClassifier())) { - if (sources) { - artifactRequests.add( - new ArtifactRequest(new SubArtifact(artifact, "sources", "jar"), repositories, null)); - } - if (javadoc) { - artifactRequests.add( - new ArtifactRequest(new SubArtifact(artifact, "javadoc", "jar"), repositories, null)); - } - } - } - } - try { - verbose("Resolving {}", artifactRequests); - context.repositorySystem().resolveArtifacts(session, artifactRequests); - } catch (ArtifactResolutionException e) { - // log - } - - info(""); - if (verbose) { - for (Artifact artifact : recorder.getAllArtifacts()) { - info("{} -> {}", artifact, artifact.getFile()); - } - } else { - info("Resolved {} artifacts", recorder.getAllArtifacts().size()); - } - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/ResolverCommandSupport.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/ResolverCommandSupport.java deleted file mode 100644 index d501756e..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/ResolverCommandSupport.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.mima.cli; - -import eu.maveniverse.maven.mima.context.Context; -import java.util.ArrayList; -import java.util.HashSet; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.resolution.ArtifactDescriptorException; -import org.eclipse.aether.resolution.ArtifactDescriptorRequest; -import org.eclipse.aether.resolution.ArtifactDescriptorResult; -import org.eclipse.aether.util.artifact.ArtifactIdUtils; -import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy; - -/** - * Support. - */ -public abstract class ResolverCommandSupport extends CommandSupport { - - protected RepositorySystemSession getRepositorySystemSession() { - return (RepositorySystemSession) getOrCreate( - RepositorySystemSession.class.getName(), () -> getContext().repositorySystemSession()); - } - - protected RemoteRepository buildRemoteRepositoryFromSpec(String remoteRepositorySpec) { - String[] parts = remoteRepositorySpec.split("::"); - if (parts.length == 1) { - return new RemoteRepository.Builder("mima", "default", parts[0]).build(); - } else if (parts.length == 2) { - return new RemoteRepository.Builder(parts[0], "default", parts[1]).build(); - } else { - throw new IllegalArgumentException("Invalid remote repository spec"); - } - } - - protected java.util.List importBoms(Context context, String... boms) - throws ArtifactDescriptorException { - HashSet keys = new HashSet<>(); - DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(context.repositorySystemSession()); - session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(false, false)); - ArrayList managedDependencies = new ArrayList<>(); - for (String bomGav : boms) { - if ("".equals(bomGav)) { - continue; - } - Artifact bom = new DefaultArtifact(bomGav); - ArtifactDescriptorRequest artifactDescriptorRequest = - new ArtifactDescriptorRequest(bom, context.remoteRepositories(), ""); - ArtifactDescriptorResult artifactDescriptorResult = - context.repositorySystem().readArtifactDescriptor(session, artifactDescriptorRequest); - artifactDescriptorResult.getManagedDependencies().forEach(d -> { - if (keys.add(ArtifactIdUtils.toVersionlessId(d.getArtifact()))) { - managedDependencies.add(d); - } else { - info("W: BOM {} introduced an already managed dependency {}", bom, d); - } - }); - } - return managedDependencies; - } - - protected Artifact parseGav(String gav, java.util.List managedDependencies) { - try { - return new DefaultArtifact(gav); - } catch (IllegalArgumentException e) { - // assume it is g:a and we have v in depMgt section - return managedDependencies.stream() - .map(Dependency::getArtifact) - .filter(a -> gav.equals(a.getGroupId() + ":" + a.getArtifactId())) - .findFirst() - .orElseThrow(() -> e); - } - } - - @Override - public final Integer call() { - try (Context context = getContext()) { - return doCall(context); - } catch (Exception e) { - error("Error", e); - return 1; - } - } - - protected Integer doCall(Context context) throws Exception { - throw new RuntimeException("Not implemented; you should override this method in subcommand"); - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Search.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Search.java deleted file mode 100644 index a800953e..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Search.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.mima.cli; - -import static org.apache.maven.search.api.request.Query.query; - -import java.io.IOException; -import org.apache.maven.search.api.SearchBackend; -import org.apache.maven.search.api.SearchRequest; -import org.apache.maven.search.api.SearchResponse; -import org.apache.maven.search.api.request.Query; -import org.eclipse.aether.artifact.DefaultArtifact; -import picocli.CommandLine; - -/** - * Searches artifacts using SMO service. - */ -@CommandLine.Command(name = "search", description = "Searches Maven Artifacts") -public final class Search extends SearchCommandSupport { - - @CommandLine.Parameters(index = "0", description = "The expression to search for") - private String expression; - - @Override - protected Integer doCall() throws IOException { - try (SearchBackend backend = getSmoBackend(repositoryId)) { - Query query; - try { - query = toSmoQuery(new DefaultArtifact(expression)); - } catch (IllegalArgumentException e) { - query = query(expression); - } - SearchRequest searchRequest = new SearchRequest(query); - SearchResponse searchResponse = backend.search(searchRequest); - - renderPage(searchResponse.getPage(), null).forEach(this::info); - while (searchResponse.getCurrentHits() > 0) { - searchResponse = - backend.search(searchResponse.getSearchRequest().nextPage()); - renderPage(searchResponse.getPage(), null).forEach(this::info); - } - } - return 0; - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/SearchCommandSupport.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/SearchCommandSupport.java deleted file mode 100644 index 8c82b22f..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/SearchCommandSupport.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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.mima.cli; - -import static org.apache.maven.search.api.request.BooleanQuery.and; -import static org.apache.maven.search.api.request.FieldQuery.fieldQuery; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; -import org.apache.maven.search.api.MAVEN; -import org.apache.maven.search.api.Record; -import org.apache.maven.search.api.SearchBackend; -import org.apache.maven.search.api.request.Query; -import org.apache.maven.search.api.transport.Java11HttpClientTransport; -import org.apache.maven.search.api.transport.Transport; -import org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchBackendFactory; -import org.apache.maven.search.backend.remoterepository.ResponseExtractor; -import org.apache.maven.search.backend.remoterepository.extractor.MavenCentralResponseExtractor; -import org.apache.maven.search.backend.remoterepository.extractor.Nx2ResponseExtractor; -import org.apache.maven.search.backend.smo.SmoSearchBackendFactory; -import org.eclipse.aether.artifact.Artifact; -import picocli.CommandLine; - -/** - * Search support. - */ -public abstract class SearchCommandSupport extends CommandSupport { - @CommandLine.Option( - names = {"--repositoryId"}, - defaultValue = "central", - description = "The targeted repository ID") - protected String repositoryId; - - @CommandLine.Option( - names = {"--repositoryBaseUri"}, - defaultValue = "https://repo.maven.apache.org/maven2/", - description = "The targeted repository base Uri") - protected String repositoryBaseUri; - - @CommandLine.Option( - names = {"--repositoryVendor"}, - defaultValue = "central", - description = "The targeted repository vendor") - protected String repositoryVendor; - - protected Transport getTransport() { - return (Transport) getOrCreate(Transport.class.getName(), Java11HttpClientTransport::new); - } - - protected SearchBackend getRemoteRepositoryBackend(String repositoryId, String baseUri, String vendor) { - final ResponseExtractor extractor; - if ("central".equals(vendor)) { - extractor = new MavenCentralResponseExtractor(); - } else if ("nx2".equals(vendor)) { - extractor = new Nx2ResponseExtractor(); - } else { - throw new IllegalArgumentException("Unknown remote vendor"); - } - return (SearchBackend) getOrCreate( - SearchBackend.class.getName() + "-" + repositoryId, - () -> RemoteRepositorySearchBackendFactory.create( - repositoryId + "-rr", repositoryId, baseUri, getTransport(), extractor)); - } - - protected SearchBackend getSmoBackend(String repositoryId) { - if (!"central".equals(repositoryId)) { - throw new IllegalArgumentException("The SMO service is offered for Central only"); - } - return SmoSearchBackendFactory.create( - repositoryId + "-smo", repositoryId, "https://search.maven.org/solrsearch/select", getTransport()); - } - - /** - * Query out of {@link Artifact} for RR backend: it maps all that are given. - */ - protected Query toRrQuery(Artifact artifact) { - Query result = fieldQuery(MAVEN.GROUP_ID, artifact.getGroupId()); - result = and(result, fieldQuery(MAVEN.ARTIFACT_ID, artifact.getArtifactId())); - result = and(result, fieldQuery(MAVEN.VERSION, artifact.getVersion())); - if (artifact.getClassifier() != null && !artifact.getClassifier().isEmpty()) { - result = and(result, fieldQuery(MAVEN.CLASSIFIER, artifact.getClassifier())); - } - result = and(result, fieldQuery(MAVEN.FILE_EXTENSION, artifact.getExtension())); - - return result; - } - - /** - * Query out of {@link Artifact} for SMO backend: SMO "have no idea" what file extension is, it handles only - * "packaging", so we map here {@link Artifact#getExtension()} into "packaging" instead. Also, if we query - * fields with value "*" SMO throws HTTP 400, so we simply omit "*" from queries, but they still allow us to - * enter "*:*:1.0" that translates to "version=1.0" query. - */ - protected Query toSmoQuery(Artifact artifact) { - Query result = null; - if (!"*".equals(artifact.getGroupId())) { - result = result != null - ? and(result, fieldQuery(MAVEN.GROUP_ID, artifact.getGroupId())) - : fieldQuery(MAVEN.GROUP_ID, artifact.getGroupId()); - } - if (!"*".equals(artifact.getArtifactId())) { - result = result != null - ? and(result, fieldQuery(MAVEN.ARTIFACT_ID, artifact.getArtifactId())) - : fieldQuery(MAVEN.ARTIFACT_ID, artifact.getArtifactId()); - } - if (!"*".equals(artifact.getVersion())) { - result = result != null - ? and(result, fieldQuery(MAVEN.VERSION, artifact.getVersion())) - : fieldQuery(MAVEN.VERSION, artifact.getVersion()); - } - if (!"*".equals(artifact.getClassifier()) && !"".equals(artifact.getClassifier())) { - result = result != null - ? and(result, fieldQuery(MAVEN.CLASSIFIER, artifact.getClassifier())) - : fieldQuery(MAVEN.CLASSIFIER, artifact.getClassifier()); - } - if (!"*".equals(artifact.getExtension())) { - result = result != null - ? and(result, fieldQuery(MAVEN.PACKAGING, artifact.getExtension())) - : fieldQuery(MAVEN.PACKAGING, artifact.getExtension()); - } - - if (result == null) { - throw new IllegalArgumentException("Too broad query expression"); - } - return result; - } - - protected java.util.List renderPage(java.util.List page, Predicate versionPredicate) { - ArrayList result = new ArrayList<>(); - for (Record record : page) { - final String version = record.getValue(MAVEN.VERSION); - if (version != null && versionPredicate != null && !versionPredicate.test(version)) { - continue; - } - StringBuilder sb = new StringBuilder(); - sb.append(record.getValue(MAVEN.GROUP_ID)); - if (record.hasField(MAVEN.ARTIFACT_ID)) { - sb.append(":").append(record.getValue(MAVEN.ARTIFACT_ID)); - } - if (record.hasField(MAVEN.VERSION)) { - sb.append(":").append(record.getValue(MAVEN.VERSION)); - } - if (record.hasField(MAVEN.PACKAGING)) { - if (record.hasField(MAVEN.CLASSIFIER)) { - sb.append(":").append(record.getValue(MAVEN.CLASSIFIER)); - } - sb.append(":").append(record.getValue(MAVEN.PACKAGING)); - } else if (record.hasField(MAVEN.FILE_EXTENSION)) { - if (record.hasField(MAVEN.CLASSIFIER)) { - sb.append(":").append(record.getValue(MAVEN.CLASSIFIER)); - } - sb.append(":").append(record.getValue(MAVEN.FILE_EXTENSION)); - } - - List remarks = new ArrayList<>(); - if (record.getLastUpdated() != null) { - remarks.add("lastUpdate=" + Instant.ofEpochMilli(record.getLastUpdated())); - } - if (record.hasField(MAVEN.VERSION_COUNT)) { - remarks.add("versionCount=" + record.getValue(MAVEN.VERSION_COUNT)); - } - if (record.hasField(MAVEN.HAS_SOURCE)) { - remarks.add("hasSource=" + record.getValue(MAVEN.HAS_SOURCE)); - } - if (record.hasField(MAVEN.HAS_JAVADOC)) { - remarks.add("hasJavadoc=" + record.getValue(MAVEN.HAS_JAVADOC)); - } - - result.add("" + sb); - if (verbose && !remarks.isEmpty()) { - result.add(" " + remarks); - } - } - return result; - } - - @Override - public final Integer call() { - try { - return doCall(); - } catch (Exception e) { - error("Error", e); - return 1; - } - } - - protected Integer doCall() throws Exception { - throw new RuntimeException("Not implemented; you should override this method in subcommand"); - } -} diff --git a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Verify.java b/cli/src/main/java/eu/maveniverse/maven/mima/cli/Verify.java deleted file mode 100644 index 97501f22..00000000 --- a/cli/src/main/java/eu/maveniverse/maven/mima/cli/Verify.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.mima.cli; - -import static org.apache.maven.search.api.request.BooleanQuery.and; -import static org.apache.maven.search.api.request.FieldQuery.fieldQuery; - -import java.io.IOException; -import org.apache.maven.search.api.MAVEN; -import org.apache.maven.search.api.SearchBackend; -import org.apache.maven.search.api.SearchRequest; -import org.apache.maven.search.api.SearchResponse; -import org.apache.maven.search.api.request.Query; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; -import picocli.CommandLine; - -/** - * Verifies artifact against known SHA-1. - */ -@CommandLine.Command(name = "verify", description = "Verifies Maven Artifact") -public final class Verify extends SearchCommandSupport { - - @CommandLine.Parameters(index = "0", description = "The GAV to check") - private String gav; - - @CommandLine.Parameters(index = "1", description = "The known SHA-1 of GAV") - private String sha1; - - @Override - protected Integer doCall() throws IOException { - try (SearchBackend backend = getRemoteRepositoryBackend(repositoryId, repositoryBaseUri, repositoryVendor)) { - Artifact artifact = new DefaultArtifact(gav); - boolean verified = verify(backend, new DefaultArtifact(gav), sha1); - - info("Artifact SHA1({})={}: {}", artifact, sha1, verified ? "MATCHED" : "NOT MATCHED"); - return verified ? 0 : 1; - } - } - - private boolean verify(SearchBackend backend, Artifact artifact, String sha1) throws IOException { - Query query = toRrQuery(artifact); - query = and(query, fieldQuery(MAVEN.SHA1, sha1)); - SearchRequest searchRequest = new SearchRequest(query); - SearchResponse searchResponse = backend.search(searchRequest); - return searchResponse.getTotalHits() == 1; - } -} diff --git a/cli/src/main/resources/simplelogger.properties b/cli/src/main/resources/simplelogger.properties deleted file mode 100644 index 77f183a5..00000000 --- a/cli/src/main/resources/simplelogger.properties +++ /dev/null @@ -1,16 +0,0 @@ -# -# 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 -# - -org.slf4j.simpleLogger.defaultLogLevel=info -org.slf4j.simpleLogger.showDateTime=false -org.slf4j.simpleLogger.showThreadName=false -org.slf4j.simpleLogger.showLogName=false -org.slf4j.simpleLogger.logFile=System.out -org.slf4j.simpleLogger.cacheOutputStream=true -org.slf4j.simpleLogger.levelInBrackets=false -org.slf4j.simpleLogger.warnLevelString=WARNING diff --git a/pom.xml b/pom.xml index ecffb22c..d6e96d85 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ context runtime extensions - cli @@ -69,10 +68,8 @@ 1.9.22 3.9.9 - 7.1.5 0.9.0.M3 1.7.36 - 4.7.6 @@ -241,45 +238,6 @@ ${version.sisu} - - - org.apache.maven.indexer - search-api - ${version.mase} - - - org.apache.maven.indexer - search-backend-remoterepository - ${version.mase} - - - org.apache.maven.indexer - search-backend-smo - ${version.mase} - - - - - info.picocli - picocli - ${version.picocli} - - - info.picocli - picocli-codegen - ${version.picocli} - - - info.picocli - picocli-shell-jline3 - ${version.picocli} - - - org.fusesource.jansi - jansi - 2.4.1 - - org.junit.jupiter junit-jupiter-api