From 9c42756b76db24986e8874eb6c59ea55abac7399 Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Wed, 10 Jul 2024 15:42:11 +0100 Subject: [PATCH] Migrate from Ivy to MIMA for resolving pkg:maven libraries. --- pom.xml | 2 +- praxiscore-bin/pom.xml | 2 +- praxiscore-code-ivy/pom.xml | 36 ---- .../src/main/java/module-info.java | 12 -- .../praxislive/code/services/ivy/Grappa.java | 177 ---------------- .../code/services/ivy/IvyResolver.java | 190 ------------------ .../code/services/ivy/MavenArtefactInfo.java | 116 ----------- ...g.praxislive.code.LibraryResolver$Provider | 1 - praxiscore-code-mima/pom.xml | 66 ++++++ .../src/main/java/module-info.java | 15 ++ .../code/services/mima/MimaResolver.java | 171 ++++++++++++++++ .../code/services/mima/MimaWrapper.java | 78 +++++++ ...g.praxislive.code.LibraryResolver$Provider | 1 + 13 files changed, 333 insertions(+), 534 deletions(-) delete mode 100644 praxiscore-code-ivy/pom.xml delete mode 100644 praxiscore-code-ivy/src/main/java/module-info.java delete mode 100644 praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/Grappa.java delete mode 100644 praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/IvyResolver.java delete mode 100644 praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/MavenArtefactInfo.java delete mode 100644 praxiscore-code-ivy/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider create mode 100644 praxiscore-code-mima/pom.xml create mode 100644 praxiscore-code-mima/src/main/java/module-info.java create mode 100644 praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaResolver.java create mode 100644 praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaWrapper.java create mode 100644 praxiscore-code-mima/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider diff --git a/pom.xml b/pom.xml index 527286fb..b8528e5b 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ praxiscore-gui praxiscore-midi praxiscore-osc - praxiscore-code-ivy + praxiscore-code-mima praxiscore-launcher-jline praxiscore-purl praxiscore-bin diff --git a/praxiscore-bin/pom.xml b/praxiscore-bin/pom.xml index 5918e73c..eba75070 100644 --- a/praxiscore-bin/pom.xml +++ b/praxiscore-bin/pom.xml @@ -108,7 +108,7 @@ ${project.groupId} - praxiscore-code-ivy + praxiscore-code-mima ${project.version} diff --git a/praxiscore-code-ivy/pom.xml b/praxiscore-code-ivy/pom.xml deleted file mode 100644 index 1ec9e0da..00000000 --- a/praxiscore-code-ivy/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - 4.0.0 - - org.praxislive - praxiscore - 6.0.0-SNAPSHOT - - praxiscore-code-ivy - jar - PraxisCORE Code Ivy - - - - ${project.groupId} - praxiscore-api - ${project.version} - - - ${project.groupId} - praxiscore-code - ${project.version} - - - ${project.groupId} - praxiscore-purl - ${project.version} - - - org.apache.ivy - ivy - 2.5.1 - - - - \ No newline at end of file diff --git a/praxiscore-code-ivy/src/main/java/module-info.java b/praxiscore-code-ivy/src/main/java/module-info.java deleted file mode 100644 index e6aea527..00000000 --- a/praxiscore-code-ivy/src/main/java/module-info.java +++ /dev/null @@ -1,12 +0,0 @@ - -module org.praxislive.code.ivy { - - requires org.praxislive.core; - requires org.praxislive.code; - requires org.praxislive.purl; - requires org.apache.ivy; - - provides org.praxislive.code.LibraryResolver.Provider with - org.praxislive.code.services.ivy.IvyResolver.Provider; - -} diff --git a/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/Grappa.java b/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/Grappa.java deleted file mode 100644 index ddf028a2..00000000 --- a/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/Grappa.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2020 Neil C Smith. - * Loosely based on GrapeIvy.groovy from Apache Groovy, and licensed under ASL. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ - -package org.praxislive.code.services.ivy; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import org.apache.ivy.Ivy; -import org.apache.ivy.core.IvyContext; -import org.apache.ivy.core.module.descriptor.Configuration; -import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor; -import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor; -import org.apache.ivy.core.module.descriptor.DefaultExcludeRule; -import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; -import org.apache.ivy.core.module.descriptor.ExcludeRule; -import org.apache.ivy.core.module.id.ArtifactId; -import org.apache.ivy.core.module.id.ModuleId; -import org.apache.ivy.core.module.id.ModuleRevisionId; -import org.apache.ivy.core.report.ResolveReport; -import org.apache.ivy.core.resolve.IvyNode; -import org.apache.ivy.core.resolve.ResolveOptions; -import org.apache.ivy.core.sort.SortOptions; -import org.apache.ivy.plugins.matcher.ExactPatternMatcher; -import org.apache.ivy.plugins.matcher.PatternMatcher; -import org.apache.ivy.util.DefaultMessageLogger; -import org.apache.ivy.util.Message; -import org.praxislive.core.Value; -import org.praxislive.core.types.PArray; - -/** - * - */ -class Grappa { - - static { - Message.setDefaultLogger(new DefaultMessageLogger(-1)); - } - - private final Ivy ivy; - - private List excludes; - - private Grappa(Ivy ivy) { - this.ivy = ivy; - excludes = List.of(); - } - - void setExcludes(String excludes) { - this.excludes = List.copyOf(parseExcludes(excludes)); - } - - - - @SuppressWarnings("unchecked") - List sort(List nodes) { - return (List) ivy.execute(new Ivy.IvyCallback() { - @Override - public Object doInIvyContext(Ivy ivy, IvyContext context) { - return ivy.sortNodes(nodes, SortOptions.DEFAULT); - } - }); - - } - - ResolveReport resolve(MavenArtefactInfo dep) { - return (ResolveReport) ivy.execute(new Ivy.IvyCallback() { - @Override - public Object doInIvyContext(Ivy ivy, IvyContext context) { - var millis = System.currentTimeMillis(); - var md = new DefaultModuleDescriptor( - ModuleRevisionId.newInstance("grappa", "all-grappa", "working" + millis), - "integration", null, true); - md.addConfiguration(new Configuration("default")); - md.setLastModified(millis); - - var version = dep.version().isBlank() ? "latest.release" : dep.version(); - var mrid = ModuleRevisionId.newInstance(dep.group(), dep.artefact(), version); - var dd = new DefaultDependencyDescriptor(md, mrid, false, false, true); - dd.addDependencyConfiguration("default", "default"); - - if (dep.classifier() != null && !dep.classifier().isBlank()) { - var dad = new DefaultDependencyArtifactDescriptor(dd, mrid.getName(), - "jar", "jar", null, Map.of("classifier", dep.classifier())); - dd.addDependencyArtifact("default", dad); - } - - md.addDependency(dd); - - excludes.forEach(md::addExcludeRule); - - var options = new ResolveOptions(); - options.setConfs(new String[]{"default"}); - options.setOutputReport(false); - - ResolveReport report = null; - int attempt = 8; - while (true) { - try { - report = ivy.resolve(md, options); - break; - } catch (Exception e) { - if (attempt-- > 0) { - try { - Thread.sleep(250); - continue; - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - } - throw new RuntimeException(e); - } - } - - var cacheManager = ivy.getResolutionCacheManager(); - cacheManager.getResolvedIvyFileInCache(md.getModuleRevisionId()).delete(); - cacheManager.getResolvedIvyPropertiesInCache(md.getModuleRevisionId()).delete(); - - return report; - - } - }); - } - - private List parseExcludes(String excludes) { - try { - var arr = PArray.parse(excludes); - List lst = new ArrayList<>(arr.size()); - for (Value v : arr) { - String[] split = v.toString().split(":"); - if (split.length != 2) { - throw new IllegalArgumentException(); - } - var exclude = new DefaultExcludeRule( - new ArtifactId(new ModuleId(split[0], split[1]), - PatternMatcher.ANY_EXPRESSION, - PatternMatcher.ANY_EXPRESSION, - PatternMatcher.ANY_EXPRESSION), - ExactPatternMatcher.INSTANCE, null); - exclude.addConfiguration("default"); - lst.add(exclude); - } - return lst; - } catch (Exception ex) { - System.getLogger(IvyResolver.class.getName()) - .log(System.Logger.Level.ERROR, "Invalid excludes", ex); - return List.of(); - } - } - - static Grappa create() throws Exception { - var ivy = Ivy.newInstance(); - ivy.configureDefault(); - return new Grappa(ivy); - } - -} diff --git a/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/IvyResolver.java b/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/IvyResolver.java deleted file mode 100644 index 7d8f1f63..00000000 --- a/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/IvyResolver.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2023 Neil C Smith. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ -package org.praxislive.code.services.ivy; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; -import org.apache.ivy.core.report.ArtifactDownloadReport; -import org.praxislive.code.LibraryResolver; -import org.praxislive.core.services.LogLevel; -import org.praxislive.core.types.PResource; -import org.praxislive.purl.PackageURL; - -/** - * - */ -public class IvyResolver implements LibraryResolver { - - private final Grappa grappa; - - private IvyResolver() { - Grappa g = null; - try { - g = Grappa.create(); - } catch (Exception ex) { - System.getLogger(IvyResolver.class.getName()) - .log(System.Logger.Level.ERROR, "Unable to initialise Ivy support", ex); - } - this.grappa = g; - } - - @Override - public Optional resolve(PResource resource, Context context) - throws Exception { - var res = resource.toString(); - if (!res.startsWith("pkg:maven/")) { - return Optional.empty(); - } - var artefact = parsePURL(res); - var installed = buildInstalledList(context.provided().collect(Collectors.toList())); - var existing = findExisting(installed, artefact); - if (existing != null) { - if (!artefact.version().isBlank() - && !Objects.equals(existing.version(), artefact.version())) { - context.log().log(LogLevel.WARNING, - resource + " already installed at version " + existing.version()); - } - return Optional.of(new Entry(toPURL(existing), List.of())); - } - - var report = grappa.resolve(artefact); - - var problemMsg = report.getAllProblemMessages().stream().collect(Collectors.joining("\n")); - - if (report.hasError()) { - throw new IllegalStateException("Error resolving " + res - + (problemMsg.isBlank() ? "" : "\n" + problemMsg)); - } else if (!problemMsg.isBlank()) { - context.log().log(LogLevel.WARNING, problemMsg); - } - - var nodes = grappa.sort(report.getDependencies()); - var installing = new ArrayList(); - var resolved = resource; - var provides = new ArrayList(); - var files = new ArrayList(); - for (var node : nodes) { - for (var download : report.getArtifactsReports(node.getResolvedId())) { - var info = toInfo(download); - var ex = findExisting(installed, info); - if (ex == null) { - var purl = toPURL(info); - if (artefact.isMatchingArtefact(info)) { - resolved = purl; - } - provides.add(purl); - installing.add(info); - var file = download.getLocalFile(); - if (file == null) { - context.log().log(LogLevel.ERROR, "No file found for " + info); - } else { - files.add(file.toPath()); - } - } else { - if (!Objects.equals(info.version(), ex.version())) { - context.log().log(LogLevel.WARNING, - "Found already installed dependency " + ex - + " instead of version " + info.version()); - } else { - context.log().log(LogLevel.INFO, - "Found already installed dependency " + ex); - } - } - } - } - - return Optional.of(new Entry(resolved, files, provides)); - } - - private static MavenArtefactInfo parsePURL(String purlString) { - var purl = PackageURL.parse(purlString); - return new MavenArtefactInfo( - purl.namespace().orElseThrow(IllegalArgumentException::new), - purl.name(), - purl.version().orElse(""), - purl.qualifiers().flatMap(q -> Optional.ofNullable(q.get("classifier"))).orElse("") - ); - } - - private static PResource toPURL(MavenArtefactInfo info) { - var builder = PackageURL.builder() - .withType("maven") - .withNamespace(info.group()) - .withName(info.artefact()); - if (!info.version().isBlank()) { - builder.withVersion(info.version()); - } - if (!info.classifier().isBlank()) { - builder.withQualifier("classifier", info.classifier()); - } - return PResource.of(builder.build().toURI()); - } - - private static MavenArtefactInfo toInfo(ArtifactDownloadReport report) { - var mrid = report.getArtifact().getId().getModuleRevisionId(); - var group = mrid.getOrganisation(); - var artefact = mrid.getName(); - var version = mrid.getRevision(); - var classifier = report.getArtifact().getId() - .getQualifiedExtraAttributes().getOrDefault("classifier", ""); - return new MavenArtefactInfo(group, artefact, version, classifier); - } - - private static List buildInstalledList(List provided) { - if (provided.isEmpty()) { - return List.of(); - } - List list = new ArrayList<>(provided.size()); - for (var res : provided) { - var str = res.toString(); - if (!str.startsWith("pkg:maven")) { - continue; - } - try { - list.add(parsePURL(str)); - } catch (Exception ex) { - // fall through - } - } - return List.copyOf(list); - } - - private static MavenArtefactInfo findExisting(List installed, MavenArtefactInfo artefact) { - return installed.stream() - .filter(artefact::isMatchingArtefact) - .findFirst().orElse(null); - } - - public static class Provider implements LibraryResolver.Provider { - - @Override - public LibraryResolver createResolver() { - return new IvyResolver(); - } - - } - -} diff --git a/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/MavenArtefactInfo.java b/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/MavenArtefactInfo.java deleted file mode 100644 index d1ce15b7..00000000 --- a/praxiscore-code-ivy/src/main/java/org/praxislive/code/services/ivy/MavenArtefactInfo.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2020 Neil C Smith. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ -package org.praxislive.code.services.ivy; - -import java.util.Objects; - -/** - * - */ -class MavenArtefactInfo { - - private final String group; - private final String artefact; - private final String version; - private final String classifier; - - public MavenArtefactInfo(String group, String artefact, String version) { - this(group, artefact, version, ""); - } - - public MavenArtefactInfo(String group, String artefact, String version, String classifier) { - this.group = group; - this.artefact = artefact; - this.version = version; - this.classifier = classifier; - } - - public String group() { - return group; - } - - public String artefact() { - return artefact; - } - - public String version() { - return version; - } - - public String classifier() { - return classifier; - } - - public boolean isMatchingArtefact(MavenArtefactInfo info) { - return Objects.equals(group, info.group()) - && Objects.equals(artefact, info.artefact()) - && Objects.equals(classifier, info.classifier()); - } - - @Override - public int hashCode() { - int hash = 7; - hash = 97 * hash + Objects.hashCode(this.group); - hash = 97 * hash + Objects.hashCode(this.artefact); - hash = 97 * hash + Objects.hashCode(this.version); - hash = 97 * hash + Objects.hashCode(this.classifier); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final MavenArtefactInfo other = (MavenArtefactInfo) obj; - if (!Objects.equals(this.group, other.group)) { - return false; - } - if (!Objects.equals(this.artefact, other.artefact)) { - return false; - } - if (!Objects.equals(this.version, other.version)) { - return false; - } - if (!Objects.equals(this.classifier, other.classifier)) { - return false; - } - return true; - } - - @Override - public String toString() { - return "MavenArtefactInfo{" + "group=" + group + "," - + "artefact=" + artefact + "," - + "version=" + version + "," - + "classifier=" + classifier + '}'; - } - - - -} diff --git a/praxiscore-code-ivy/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider b/praxiscore-code-ivy/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider deleted file mode 100644 index 53079287..00000000 --- a/praxiscore-code-ivy/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider +++ /dev/null @@ -1 +0,0 @@ -org.praxislive.code.services.ivy.IvyResolver$Provider diff --git a/praxiscore-code-mima/pom.xml b/praxiscore-code-mima/pom.xml new file mode 100644 index 00000000..2849efcf --- /dev/null +++ b/praxiscore-code-mima/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.praxislive + praxiscore + 6.0.0-SNAPSHOT + + praxiscore-code-mima + jar + PraxisCORE Code MIMA + + + + ${project.groupId} + praxiscore-api + ${project.version} + + + ${project.groupId} + praxiscore-code + ${project.version} + + + ${project.groupId} + praxiscore-purl + ${project.version} + + + eu.maveniverse.maven.mima + context + 2.4.15 + + + org.apache.maven.resolver + maven-resolver-api + + + org.apache.maven.resolver + maven-resolver-util + + + + + eu.maveniverse.maven.mima.runtime + standalone-static-uber + 2.4.15 + + + org.slf4j + slf4j-api + 2.0.13 + + + org.slf4j + slf4j-nop + 2.0.13 + + + org.slf4j + jcl-over-slf4j + 2.0.13 + + + + diff --git a/praxiscore-code-mima/src/main/java/module-info.java b/praxiscore-code-mima/src/main/java/module-info.java new file mode 100644 index 00000000..96a1c0c8 --- /dev/null +++ b/praxiscore-code-mima/src/main/java/module-info.java @@ -0,0 +1,15 @@ + +module org.praxislive.code.mima { + + requires org.praxislive.core; + requires org.praxislive.code; + requires org.praxislive.purl; + requires org.slf4j; + requires org.apache.commons.logging; + requires eu.maveniverse.maven.mima.context; + requires eu.maveniverse.maven.mima.runtime.standalonestatic; + + provides org.praxislive.code.LibraryResolver.Provider with + org.praxislive.code.services.mima.MimaResolver.Provider; + +} diff --git a/praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaResolver.java b/praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaResolver.java new file mode 100644 index 00000000..6c21e57a --- /dev/null +++ b/praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaResolver.java @@ -0,0 +1,171 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.code.services.mima; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.praxislive.code.LibraryResolver; +import org.praxislive.core.services.LogLevel; +import org.praxislive.core.types.PResource; +import org.praxislive.purl.PackageURL; + +/** + * + */ +public class MimaResolver implements LibraryResolver { + + MimaWrapper mima; + + + @Override + public Optional resolve(PResource resource, Context context) throws Exception { + String res = resource.toString(); + if (!res.startsWith("pkg:maven/")) { + return Optional.empty(); + } + Artifact artifact = parsePURL(res); + List installed = buildInstalledList(context.provided().toList()); + Artifact existing = findExisting(installed, artifact); + if (existing != null) { + if (!artifact.getVersion().isBlank() + && !Objects.equals(existing.getVersion(), artifact.getVersion())) { + context.log().log(LogLevel.WARNING, + resource + " already installed at version " + existing.getVersion()); + } + return Optional.of(new Entry(toPURL(existing), List.of())); + } + if (mima == null) { + mima = new MimaWrapper(); + } + List resolved = mima.resolve(artifact, installed); + PResource root = resource; + List provides = new ArrayList<>(); + List files = new ArrayList<>(); + + for (Artifact dep : resolved) { + Artifact ex = findExisting(installed, dep); + if (ex == null) { + Path file = dep.getFile().toPath(); + PResource purl = toPURL(dep); + if (matchingArtifacts(dep, artifact)) { + root = purl; + } + provides.add(purl); + files.add(file); + } else { + if (!Objects.equals(dep.getVersion(), ex.getVersion())) { + context.log().log(LogLevel.WARNING, + "Found already installed dependency " + toPURL(ex) + + " instead of version " + dep.getVersion()); + } else { + context.log().log(LogLevel.INFO, + "Found already installed dependency " + toPURL(ex)); + } + } + } + + return Optional.of(new Entry(root, files, provides)); + } + + @Override + public void dispose() { + if (mima != null) { + mima.dispose(); + mima = null; + } + } + + private static Artifact parsePURL(String purlString) { + PackageURL purl = PackageURL.parse(purlString); + return new DefaultArtifact( + purl.namespace().orElseThrow(IllegalArgumentException::new), + purl.name(), + purl.qualifiers().flatMap(q -> Optional.ofNullable(q.get("classifier"))).orElse(""), + purl.qualifiers().flatMap(q -> Optional.ofNullable(q.get("type"))).orElse("jar"), + purl.version().orElse("")); + } + + private static PResource toPURL(Artifact artifact) { + PackageURL.Builder builder = PackageURL.builder() + .withType("maven") + .withNamespace(artifact.getGroupId()) + .withName(artifact.getArtifactId()); + if (!artifact.getVersion().isBlank()) { + builder.withVersion(artifact.getVersion()); + } + if (!artifact.getClassifier().isBlank()) { + builder.withQualifier("classifier", artifact.getClassifier()); + } + if (!"jar".equals(artifact.getExtension())) { + builder.withQualifier("type", artifact.getExtension()); + } + return PResource.of(builder.build().toURI()); + } + + private static List buildInstalledList(List provided) { + if (provided.isEmpty()) { + return List.of(); + } + List list = new ArrayList<>(provided.size()); + for (var res : provided) { + var str = res.toString(); + if (!str.startsWith("pkg:maven/")) { + continue; + } + try { + list.add(parsePURL(str)); + } catch (Exception ex) { + // fall through + } + } + return List.copyOf(list); + } + + private static Artifact findExisting(List installed, Artifact artifact) { + return installed.stream() + .filter(lib -> matchingArtifacts(lib, artifact)) + .findFirst().orElse(null); + } + + private static boolean matchingArtifacts(Artifact art1, Artifact art2) { + return Objects.equals(art1, art2) + || (Objects.equals(art1.getGroupId(), art2.getGroupId()) + && Objects.equals(art1.getArtifactId(), art2.getArtifactId()) + && Objects.equals(art1.getClassifier(), art2.getClassifier()) + && Objects.equals(art1.getExtension(), art2.getExtension())); + } + + public static final class Provider implements LibraryResolver.Provider { + + @Override + public LibraryResolver createResolver() { + return new MimaResolver(); + } + + } + +} diff --git a/praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaWrapper.java b/praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaWrapper.java new file mode 100644 index 00000000..5f3d5045 --- /dev/null +++ b/praxiscore-code-mima/src/main/java/org/praxislive/code/services/mima/MimaWrapper.java @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.code.services.mima; + +import eu.maveniverse.maven.mima.context.Context; +import eu.maveniverse.maven.mima.context.ContextOverrides; +import eu.maveniverse.maven.mima.context.Runtime; +import eu.maveniverse.maven.mima.context.Runtimes; +import java.util.List; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResolutionException; +import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.util.artifact.JavaScopes; + +/** + * + */ +class MimaWrapper { + + private final Context context; + + MimaWrapper() { + Runtime runtime = Runtimes.INSTANCE.getRuntime(); + context = runtime.create(ContextOverrides.create().withUserSettings(true).build()); + } + + List resolve(Artifact artifact, List existing) throws DependencyResolutionException { + Dependency dependency = new Dependency(artifact, JavaScopes.COMPILE); + + CollectRequest collectRequest = new CollectRequest(); + collectRequest.setRoot(dependency); + collectRequest.setRepositories(context.remoteRepositories()); + if (existing != null && !existing.isEmpty()) { + collectRequest.setManagedDependencies(existing.stream() + .map(a -> new Dependency(a, JavaScopes.COMPILE)) + .toList() + ); + } + + DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null); + DependencyResult dependencyResult = context.repositorySystem() + .resolveDependencies(context.repositorySystemSession(), dependencyRequest); + List artifacts = dependencyResult.getArtifactResults(); + return artifacts.stream() + .filter(ArtifactResult::isResolved) + .map(ArtifactResult::getArtifact) + .toList(); + + } + + void dispose() { + context.close(); + } + +} diff --git a/praxiscore-code-mima/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider b/praxiscore-code-mima/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider new file mode 100644 index 00000000..0954c2e9 --- /dev/null +++ b/praxiscore-code-mima/src/main/resources/META-INF/services/org.praxislive.code.LibraryResolver$Provider @@ -0,0 +1 @@ +org.praxislive.code.services.mima.MimaResolver$Provider