diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/CommandSupport.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/CommandSupport.java index af31aa7d..63f916b2 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/CommandSupport.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/CommandSupport.java @@ -25,6 +25,7 @@ import eu.maveniverse.maven.mima.context.Runtime; import eu.maveniverse.maven.mima.context.Runtimes; import eu.maveniverse.maven.toolbox.shared.Output; +import eu.maveniverse.maven.toolbox.shared.ToolboxCommando; import java.io.PrintStream; import java.nio.file.Path; import java.util.ArrayDeque; @@ -46,7 +47,7 @@ /** * Support class. */ -public abstract class CommandSupport implements Callable { +public abstract class CommandSupport implements Callable, CommandLine.IVersionProvider { @CommandLine.Option( names = {"-v", "--verbose"}, description = "Be verbose about things happening") @@ -137,9 +138,21 @@ protected void push(String key, Object object) { deque.push(object); } + @Override + public String[] getVersion() { + return new String[] { + "MIMA " + getRuntime().version(), + "Toolbox " + ToolboxCommando.getOrCreate(getContext()).getVersion() + }; + } + protected void mayDumpEnv(Runtime runtime, Context context, boolean verbose) { - normal("MIMA (Runtime '{}' version {})", runtime.name(), runtime.version()); - normal("===="); + warn( + "Toolbox {} (MIMA Runtime '{}' version {})", + ToolboxCommando.getOrCreate(getContext()).getVersion(), + runtime.name(), + runtime.version()); + warn("======="); normal(" Maven version {}", runtime.mavenVersion()); normal(" Managed {}", runtime.managedRepositorySystem()); normal(" Basedir {}", context.basedir()); diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Deploy.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Deploy.java index 8d9c69ed..cd255cb8 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Deploy.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Deploy.java @@ -8,13 +8,10 @@ package eu.maveniverse.maven.toolbox.cli; import eu.maveniverse.maven.mima.context.Context; +import eu.maveniverse.maven.toolbox.shared.Artifacts; import eu.maveniverse.maven.toolbox.shared.ToolboxCommando; import java.nio.file.Path; -import java.util.ArrayList; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.deployment.DeploymentException; -import org.eclipse.aether.util.artifact.SubArtifact; import picocli.CommandLine; /** @@ -23,23 +20,43 @@ @CommandLine.Command(name = "deploy", description = "Deploys Maven Artifacts") public final class Deploy extends ResolverCommandSupport { - @CommandLine.Parameters(index = "0", description = "The GAV to deploy") + @CommandLine.Parameters(index = "0", description = "The RemoteRepository spec (id::url)", arity = "1") + private String remoteRepositorySpec; + + @CommandLine.Parameters(index = "1", description = "The GAV to install", arity = "1") private String gav; - @CommandLine.Parameters(index = "1", description = "The artifact JAR file") + @CommandLine.Parameters(index = "2", description = "The artifact JAR file", arity = "1") private Path jar; - @CommandLine.Parameters(index = "2", description = "The artifact POM file") + @CommandLine.Option( + names = {"--pom"}, + description = "The POM to deploy") private Path pom; - @CommandLine.Parameters(index = "3", description = "The RemoteRepository spec (id::url)") - private String remoteRepositorySpec; + @CommandLine.Option( + names = {"--sources"}, + description = "The sources JAR to deploy") + private Path sources; + + @CommandLine.Option( + names = {"--javadoc"}, + description = "The javadoc JAR to deploy") + private Path javadoc; @Override protected boolean doCall(Context context) throws DeploymentException { - ArrayList artifacts = new ArrayList<>(); - artifacts.add(new DefaultArtifact(gav).setFile(jar.toFile())); - artifacts.add(new SubArtifact(artifacts.get(0), "", "pom").setFile(pom.toFile())); + Artifacts artifacts = new Artifacts(gav); + artifacts.addMain(jar); + if (pom != null) { + artifacts.addPom(pom); + } + if (sources != null) { + artifacts.addSources(sources); + } + if (javadoc != null) { + artifacts.addJavadoc(javadoc); + } return ToolboxCommando.getOrCreate(getContext()).deploy(remoteRepositorySpec, artifacts, output); } } diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/DeployRecorded.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/DeployRecorded.java index b2f36c76..661deb40 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/DeployRecorded.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/DeployRecorded.java @@ -9,7 +9,6 @@ import eu.maveniverse.maven.mima.context.Context; import eu.maveniverse.maven.toolbox.shared.ToolboxCommando; -import java.util.HashSet; import org.eclipse.aether.deployment.DeploymentException; import picocli.CommandLine; @@ -23,13 +22,6 @@ public final class DeployRecorded extends ResolverCommandSupport { @Override protected boolean doCall(Context context) throws DeploymentException { - normal("Deploying recorded"); - - ToolboxCommando toolboxCommando = ToolboxCommando.getOrCreate(getContext()); - toolboxCommando.artifactRecorder().setActive(false); - return toolboxCommando.deploy( - remoteRepositorySpec, - new HashSet<>(toolboxCommando.artifactRecorder().getAllArtifacts()), - output); + return ToolboxCommando.getOrCreate(getContext()).deployAllRecorded(remoteRepositorySpec, true, output); } } diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Install.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Install.java index 54ef51c4..ea83bfe7 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Install.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Install.java @@ -8,13 +8,10 @@ package eu.maveniverse.maven.toolbox.cli; import eu.maveniverse.maven.mima.context.Context; +import eu.maveniverse.maven.toolbox.shared.Artifacts; import eu.maveniverse.maven.toolbox.shared.ToolboxCommando; import java.nio.file.Path; -import java.util.ArrayList; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.installation.InstallationException; -import org.eclipse.aether.util.artifact.SubArtifact; import picocli.CommandLine; /** @@ -23,20 +20,40 @@ @CommandLine.Command(name = "install", description = "Installs Maven Artifacts") public final class Install extends ResolverCommandSupport { - @CommandLine.Parameters(index = "0", description = "The GAV to install") + @CommandLine.Parameters(index = "0", description = "The GAV to install", arity = "1") private String gav; - @CommandLine.Parameters(index = "1", description = "The artifact JAR file") + @CommandLine.Parameters(index = "1", description = "The artifact JAR file", arity = "1") private Path jar; - @CommandLine.Parameters(index = "2", description = "The artifact POM file") + @CommandLine.Option( + names = {"--pom"}, + description = "The POM to install") private Path pom; + @CommandLine.Option( + names = {"--sources"}, + description = "The sources JAR to install") + private Path sources; + + @CommandLine.Option( + names = {"--javadoc"}, + description = "The javadoc JAR to install") + private Path javadoc; + @Override protected boolean doCall(Context context) throws InstallationException { - ArrayList artifacts = new ArrayList<>(); - artifacts.add(new DefaultArtifact(gav).setFile(jar.toFile())); - artifacts.add(new SubArtifact(artifacts.get(0), "", "pom").setFile(pom.toFile())); + Artifacts artifacts = new Artifacts(gav); + artifacts.addMain(jar); + if (pom != null) { + artifacts.addPom(pom); + } + if (sources != null) { + artifacts.addSources(sources); + } + if (javadoc != null) { + artifacts.addJavadoc(javadoc); + } return ToolboxCommando.getOrCreate(getContext()).install(artifacts, output); } } diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/ListAvailablePlugins.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/ListAvailablePlugins.java index fb688970..903b3ab5 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/ListAvailablePlugins.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/ListAvailablePlugins.java @@ -9,7 +9,6 @@ import eu.maveniverse.maven.mima.context.Context; import eu.maveniverse.maven.toolbox.shared.ToolboxCommando; -import java.util.Collections; import picocli.CommandLine; /** @@ -18,11 +17,11 @@ @CommandLine.Command(name = "listAvailablePlugins", description = "List available plugins") public final class ListAvailablePlugins extends ResolverCommandSupport { - @CommandLine.Parameters(index = "0", description = "The G to list") - private String g; + @CommandLine.Parameters(index = "*", description = "The G to list", arity = "1") + private java.util.List groupIds; @Override protected boolean doCall(Context context) throws Exception { - return ToolboxCommando.getOrCreate(context).listAvailablePlugins(Collections.singletonList(g), output); + return ToolboxCommando.getOrCreate(context).listAvailablePlugins(groupIds, output); } } diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Main.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Main.java index 90a0a4e1..16602fcf 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Main.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Main.java @@ -14,7 +14,7 @@ * Main. */ @CommandLine.Command( - name = "mima", + name = "toolbox", subcommands = { Classpath.class, Deploy.class, @@ -35,14 +35,9 @@ Verify.class }, versionProvider = Main.class, - description = "MIMA CLI", + description = "Toolbox CLI", mixinStandardHelpOptions = true) -public class Main extends CommandSupport implements CommandLine.IVersionProvider { - @Override - public String[] getVersion() { - return new String[] {"MIMA " + getRuntime().version()}; - } - +public class Main extends CommandSupport { @Override protected boolean doCall(Context context) { mayDumpEnv(getRuntime(), context, false); diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Record.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Record.java index b793daf8..1671c718 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Record.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Record.java @@ -18,8 +18,17 @@ @CommandLine.Command(name = "record", description = "Records resolved Maven Artifacts") public final class Record extends ResolverCommandSupport { + @CommandLine.Option( + names = {"--stop"}, + description = "Stop recording (otherwise it starts it)") + private boolean stop; + @Override protected boolean doCall(Context context) throws DependencyResolutionException { - return ToolboxCommando.getOrCreate(context).artifactRecorder().setActive(true); + if (stop) { + return ToolboxCommando.getOrCreate(context).recordStop(output); + } else { + return ToolboxCommando.getOrCreate(context).recordStart(output); + } } } diff --git a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Repl.java b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Repl.java index e58b396c..479bac06 100644 --- a/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Repl.java +++ b/cli/src/main/java/eu/maveniverse/maven/toolbox/cli/Repl.java @@ -36,7 +36,7 @@ public class Repl extends CommandSupport { @Override public boolean doCall(Context context) { Class tp = JansiTerminalProvider.class; - push(Context.class.getName(), context); + push(Context.class.getName(), context.customize(context.contextOverrides())); // set up JLine built-in commands ConfigurationPath configPath = new ConfigurationPath(context.basedir(), context.basedir()); @@ -90,8 +90,6 @@ public boolean doCall(Context context) { } catch (Exception e) { error("REPL Failure: ", e); return false; - } finally { - context.close(); } } } diff --git a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/Artifacts.java b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/Artifacts.java new file mode 100644 index 00000000..8d2ef9ec --- /dev/null +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/Artifacts.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023-2024 Maveniverse Org. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + */ +package eu.maveniverse.maven.toolbox.shared; + +import static java.util.Objects.requireNonNull; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; + +/** + * Construction to group artifacts, make them "like a project is". + *

+ * Warning: this abstraction uses extension and not packaging, as this one does not create POM, it is caller obligation. + */ +public final class Artifacts implements Supplier> { + private final String groupId; + private final String artifactId; + private final String version; + private final String extension; + private final Map artifacts; + + public Artifacts(String gav) { + DefaultArtifact prototype = new DefaultArtifact(gav); + this.groupId = prototype.getGroupId(); + this.artifactId = prototype.getArtifactId(); + this.version = prototype.getVersion(); + this.extension = prototype.getExtension(); + this.artifacts = new HashMap<>(); + } + + public void addPom(Path artifact) { + addArtifact(null, "pom", artifact); + } + + public void addMain(Path artifact) { + addArtifact(null, extension, artifact); + } + + public void addSources(Path artifact) { + addArtifact("sources", "jar", artifact); + } + + public void addJavadoc(Path artifact) { + addArtifact("javadoc", "jar", artifact); + } + + public void addArtifact(String classifier, String extension, Path artifact) { + requireNonNull(extension, "extension"); + requireNonNull(artifact, "artifact"); + if (!Files.exists(artifact) || Files.isDirectory(artifact)) { + throw new IllegalArgumentException("artifact backing file must exist and cannot be a directory"); + } + CE ce = new CE(classifier, extension); + if (artifacts.containsKey(ce)) { + throw new IllegalArgumentException("artifact already present"); + } + artifacts.put(ce, artifact); + } + + @Override + public List get() { + ArrayList result = new ArrayList<>(artifacts.size()); + artifacts.forEach((ce, path) -> result.add( + new DefaultArtifact(groupId, artifactId, ce.classifier, ce.extension, version).setFile(path.toFile()))); + return result; + } + + private static final class CE { + private final String classifier; + private final String extension; + + private CE(String classifier, String extension) { + this.classifier = classifier; + this.extension = requireNonNull(extension, "extension"); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CE ce = (CE) o; + return Objects.equals(classifier, ce.classifier) && Objects.equals(extension, ce.extension); + } + + @Override + public int hashCode() { + return Objects.hash(classifier, extension); + } + + @Override + public String toString() { + return (classifier == null ? "" : classifier) + "." + extension; + } + } +} 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 d41d4672..bdfb4bfb 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 @@ -15,6 +15,7 @@ import java.io.Closeable; import java.io.IOException; import java.util.Collection; +import java.util.function.Supplier; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.repository.RemoteRepository; @@ -64,20 +65,26 @@ static void unset(Context context) { ToolboxSearchApi toolboxSearchApi(); - ArtifactRecorder artifactRecorder(); + String getVersion(); // Resolver related commands: they target current context contained RemoteRepository boolean classpath(ResolutionScope resolutionScope, ResolutionRoot resolutionRoot, Output output); - boolean deploy(String remoteRepositorySpec, Collection artifacts, Output output); + boolean deploy(String remoteRepositorySpec, Supplier> artifactSupplier, Output output); - boolean install(Collection artifacts, Output output); + boolean deployAllRecorded(String remoteRepositorySpec, boolean stopRecording, Output output); + + boolean install(Supplier> artifactSupplier, Output output); boolean listRepositories(ResolutionScope resolutionScope, ResolutionRoot resolutionRoot, Output output); boolean listAvailablePlugins(Collection groupIds, Output output); + boolean recordStart(Output output); + + boolean recordStop(Output output); + boolean resolve( ResolutionScope resolutionScope, ResolutionRoot resolutionRoot, diff --git a/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ArtifactMatcher.java b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ArtifactMatcher.java new file mode 100644 index 00000000..301f17b5 --- /dev/null +++ b/shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/ArtifactMatcher.java @@ -0,0 +1,124 @@ +/* + * 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 java.util.Arrays; +import java.util.Collection; +import java.util.Objects; +import java.util.function.Predicate; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; + +/** + * Simple artifact matcher. Supports {@code "*"} pattern as "any", and {@code "xxx*"} as "starts with" and + * {@code "*xxx"} as "ends with". + */ +public interface ArtifactMatcher extends Predicate { + static ArtifactMatcher not(ArtifactMatcher matcher) { + return new ArtifactMatcher() { + @Override + public boolean test(Artifact artifact) { + return !matcher.test(artifact); + } + }; + } + + static ArtifactMatcher and(ArtifactMatcher... matchers) { + return and(Arrays.asList(matchers)); + } + + static ArtifactMatcher and(Collection matchers) { + return new ArtifactMatcher() { + @Override + public boolean test(Artifact artifact) { + for (ArtifactMatcher matcher : matchers) { + if (!matcher.test(artifact)) { + return false; + } + } + return true; + } + }; + } + + static ArtifactMatcher or(ArtifactMatcher... matchers) { + return or(Arrays.asList(matchers)); + } + + static ArtifactMatcher or(Collection matchers) { + return new ArtifactMatcher() { + @Override + public boolean test(Artifact artifact) { + for (ArtifactMatcher matcher : matchers) { + if (matcher.test(artifact)) { + return true; + } + } + return false; + } + }; + } + + private static boolean isAny(String str) { + return "*".equals(str); + } + + private static boolean matches(String pattern, String str) { + if (isAny(pattern)) { + return true; + } else if (pattern.endsWith("*")) { + return str.startsWith(pattern.substring(0, pattern.length() - 1)); + } else if (pattern.startsWith("*")) { + return str.endsWith(pattern.substring(0, pattern.length() - 1)); + } else { + return Objects.equals(pattern, str); + } + } + + private static Artifact parsePrototype(String coordinate) { + requireNonNull(coordinate, "coordinate"); + Artifact s; + String[] parts = coordinate.split(":", -1); + switch (parts.length) { + case 1: + s = new DefaultArtifact(parts[0], "*", "*", "*", "*"); + break; + case 2: + s = new DefaultArtifact(parts[0], parts[1], "*", "*", "*"); + break; + case 3: + s = new DefaultArtifact(parts[0], parts[1], "*", "*", parts[2]); + break; + case 4: + s = new DefaultArtifact(parts[0], parts[1], "*", parts[2], parts[3]); + break; + case 5: + s = new DefaultArtifact(parts[0], parts[1], parts[2], parts[3], parts[4]); + break; + default: + throw new IllegalArgumentException("Bad artifact coordinates " + coordinate + + ", expected format is :[:[:]]:"); + } + return s; + } + + static Predicate withoutClassifier() { + return a -> a.getClassifier() == null || a.getClassifier().trim().isEmpty(); + } + + static Predicate artifactPredicate(String coordinate) { + Artifact prototype = parsePrototype(coordinate); + return a -> matches(prototype.getGroupId(), a.getGroupId()) + && matches(prototype.getArtifactId(), a.getArtifactId()) + && matches(prototype.getVersion(), a.getVersion()) + && matches(prototype.getExtension(), a.getExtension()) + && matches(prototype.getClassifier(), a.getClassifier()); + } +} 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 6f353a5b..2107f14f 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 @@ -8,13 +8,14 @@ package eu.maveniverse.maven.toolbox.shared.internal; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toMap; import static org.apache.maven.search.api.request.BooleanQuery.and; import static org.apache.maven.search.api.request.FieldQuery.fieldQuery; import static org.apache.maven.search.api.request.Query.query; import eu.maveniverse.maven.mima.context.Context; import eu.maveniverse.maven.mima.context.ContextOverrides; -import eu.maveniverse.maven.toolbox.shared.ArtifactRecorder; +import eu.maveniverse.maven.mima.context.internal.RuntimeSupport; import eu.maveniverse.maven.toolbox.shared.Output; import eu.maveniverse.maven.toolbox.shared.ResolutionRoot; import eu.maveniverse.maven.toolbox.shared.ResolutionScope; @@ -24,6 +25,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; @@ -32,15 +34,20 @@ import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; 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.DefaultRepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.collection.CollectResult; @@ -58,6 +65,7 @@ import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper; import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor; +import org.eclipse.aether.util.listener.ChainedRepositoryListener; import org.eclipse.aether.util.version.GenericVersionScheme; import org.eclipse.aether.version.InvalidVersionSpecificationException; import org.eclipse.aether.version.VersionConstraint; @@ -68,17 +76,20 @@ public class ToolboxCommandoImpl implements ToolboxCommando { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Context context; - private final ToolboxResolverImpl toolboxResolver; private final ToolboxSearchApiImpl toolboxSearchApi; private final ArtifactRecorderImpl artifactRecorder; + private final ToolboxResolverImpl toolboxResolver; public ToolboxCommandoImpl(Context context) { requireNonNull(context, "context"); this.context = context; - this.toolboxResolver = new ToolboxResolverImpl( - context.repositorySystem(), context.repositorySystemSession(), context.remoteRepositories()); this.toolboxSearchApi = new ToolboxSearchApiImpl(); this.artifactRecorder = new ArtifactRecorderImpl(); + DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(context.repositorySystemSession()); + session.setRepositoryListener( + ChainedRepositoryListener.newInstance(session.getRepositoryListener(), artifactRecorder)); + this.toolboxResolver = + new ToolboxResolverImpl(context.repositorySystem(), session, context.remoteRepositories()); } @Override @@ -102,8 +113,8 @@ public ToolboxSearchApi toolboxSearchApi() { } @Override - public ArtifactRecorder artifactRecorder() { - return artifactRecorder; + public String getVersion() { + return discoverArtifactVersion("eu.maveniverse.maven.toolbox", "shared", "unknown"); } @Override @@ -128,8 +139,9 @@ public boolean classpath(ResolutionScope resolutionScope, ResolutionRoot resolut } @Override - public boolean deploy(String remoteRepositorySpec, Collection artifacts, Output output) { + public boolean deploy(String remoteRepositorySpec, Supplier> artifactSupplier, Output output) { try { + Collection artifacts = artifactSupplier.get(); RemoteRepository remoteRepository = toolboxResolver.parseDeploymentRemoteRepository(remoteRepositorySpec); DeployRequest deployRequest = new DeployRequest(); deployRequest.setRepository(remoteRepository); @@ -144,8 +156,15 @@ public boolean deploy(String remoteRepositorySpec, Collection artifact } @Override - public boolean install(Collection artifacts, Output output) { + public boolean deployAllRecorded(String remoteRepositorySpec, boolean stopRecording, Output output) { + artifactRecorder.setActive(!stopRecording); + return deploy(remoteRepositorySpec, () -> new HashSet<>(artifactRecorder.getAllArtifacts()), output); + } + + @Override + public boolean install(Supplier> artifactSupplier, Output output) { try { + Collection artifacts = artifactSupplier.get(); InstallRequest installRequest = new InstallRequest(); artifacts.forEach(installRequest::addArtifact); context.repositorySystem().install(context.repositorySystemSession(), installRequest); @@ -187,10 +206,28 @@ public boolean visitLeave(DependencyNode node) { @Override public boolean listAvailablePlugins(Collection groupIds, Output output) { + output.verbose("Listing plugins in groupIds: {}", groupIds); toolboxResolver.listAvailablePlugins(groupIds).forEach(p -> output.normal(p.toString())); return true; } + @Override + public boolean recordStart(Output output) { + output.verbose("Starting recorder..."); + artifactRecorder.clear(); + artifactRecorder.setActive(true); + return true; + } + + @Override + public boolean recordStop(Output output) { + output.verbose("Stopping recorder..."); + artifactRecorder.setActive(false); + output.verbose( + "Recorded {} artifacts", artifactRecorder.getAllArtifacts().size()); + return true; + } + @Override public boolean resolve( ResolutionScope resolutionScope, @@ -242,21 +279,6 @@ public boolean resolve( } } - public static String humanReadableByteCountBin(long bytes) { - long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); - if (absB < 1024) { - return bytes + " B"; - } - long value = absB; - CharacterIterator ci = new StringCharacterIterator("KMGTPE"); - for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) { - value >>= 10; - ci.next(); - } - value *= Long.signum(bytes); - return String.format("%.1f %ciB", value / 1024.0, ci.current()); - } - @Override public boolean tree( ResolutionScope resolutionScope, ResolutionRoot resolutionRoot, boolean verbose, Output output) { @@ -461,4 +483,51 @@ public boolean verify(RemoteRepository remoteRepository, String gav, String sha1 return verified; } } + + // Utils + + public static String humanReadableByteCountBin(long bytes) { + long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); + if (absB < 1024) { + return bytes + " B"; + } + long value = absB; + CharacterIterator ci = new StringCharacterIterator("KMGTPE"); + for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) { + value >>= 10; + ci.next(); + } + value *= Long.signum(bytes); + return String.format("%.1f %ciB", value / 1024.0, ci.current()); + } + + public static String discoverArtifactVersion(String groupId, String artifactId, String defVal) { + Map mavenPomProperties = loadPomProperties(groupId, artifactId); + String versionString = mavenPomProperties.getOrDefault("version", "").trim(); + if (!versionString.startsWith("${")) { + return versionString; + } + return defVal; + } + + protected static Map loadPomProperties(String groupId, String artifactId) { + return loadClasspathProperties("/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties"); + } + + protected static Map loadClasspathProperties(String resource) { + final Properties props = new Properties(); + try (InputStream is = RuntimeSupport.class.getResourceAsStream(resource)) { + if (is != null) { + props.load(is); + } + } catch (IOException e) { + // fall through + } + return props.entrySet().stream() + .collect(toMap( + e -> String.valueOf(e.getKey()), + e -> String.valueOf(e.getValue()), + (prev, next) -> next, + HashMap::new)); + } } 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 bad16e5c..d23bb17f 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 @@ -252,7 +252,7 @@ private CollectResult doCollect( session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, ConflictResolver.Verbosity.FULL); session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); } - logger.debug("Resolving scope: {}", resolutionScope.name()); + logger.debug("Collecting scope: {}", resolutionScope.name()); CollectRequest collectRequest = new CollectRequest(); if (rootDependency != null) {