From 57a107b0eced656afc86f84cd95d4dd5e23dcdbb Mon Sep 17 00:00:00 2001 From: Mikhail Dzianishchyts Date: Fri, 21 Feb 2025 14:55:08 +0300 Subject: [PATCH] #55 Replace ivy with maven resolver --- jjava/pom.xml | 28 +- .../org/dflib/jjava/magics/MavenResolver.java | 390 ++++++------------ .../dependencies/CommonRepositories.java | 52 +-- .../jjava/magics/dependencies/Maven.java | 227 ---------- .../jjava/magics/dependencies/MavenToIvy.java | 65 --- .../jjava/jupyter/kernel/KernelMagicIT.java | 15 +- pom.xml | 30 +- 7 files changed, 185 insertions(+), 622 deletions(-) delete mode 100644 jjava/src/main/java/org/dflib/jjava/magics/dependencies/Maven.java delete mode 100644 jjava/src/main/java/org/dflib/jjava/magics/dependencies/MavenToIvy.java diff --git a/jjava/pom.xml b/jjava/pom.xml index a326bf6..6d09060 100644 --- a/jjava/pom.xml +++ b/jjava/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -24,13 +25,21 @@ ${project.version} - org.apache.ivy - ivy + eu.maveniverse.maven.mima + context + + + eu.maveniverse.maven.mima.runtime + standalone-static org.apache.maven maven-model-builder + + org.apache.maven + maven-model + org.junit.jupiter junit-jupiter-api @@ -75,17 +84,13 @@ org.dflib.jjava.shaded.com.google - com.neilalexander.jnacl - org.dflib.jjava.shaded.com.neilalexander.jnacl + eu.neilalexander.jnacl + org.dflib.jjava.shaded.eu.neilalexander.jnacl org.apache.commons.lang3 org.dflib.jjava.shaded.org.apache.commons.lang3 - - org.apache.ivy - org.dflib.jjava.shaded.org.apache.ivy - org.apache.maven @@ -109,7 +114,7 @@ * - module-info.class + **/module-info.class META-INF/*.MF META-INF/DEPENDENCIES @@ -121,6 +126,9 @@ false + + META-INF/sisu/javax.inject.Named + org.dflib.jjava.JJava diff --git a/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java b/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java index eb888af..8f90e80 100644 --- a/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java +++ b/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java @@ -23,31 +23,29 @@ */ package org.dflib.jjava.magics; -import org.apache.ivy.Ivy; -import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; -import org.apache.ivy.core.module.descriptor.ModuleDescriptor; -import org.apache.ivy.core.module.id.ModuleRevisionId; -import org.apache.ivy.core.report.ArtifactDownloadReport; -import org.apache.ivy.core.report.ResolveReport; -import org.apache.ivy.core.resolve.ResolveOptions; -import org.apache.ivy.core.settings.IvySettings; -import org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser; -import org.apache.ivy.plugins.repository.url.URLResource; -import org.apache.ivy.plugins.resolver.ChainResolver; -import org.apache.ivy.plugins.resolver.DependencyResolver; -import org.apache.ivy.util.DefaultMessageLogger; -import org.apache.ivy.util.Message; -import org.apache.ivy.util.MessageLogger; +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 org.apache.maven.model.Model; -import org.apache.maven.model.building.ModelBuildingException; +import org.apache.maven.model.building.DefaultModelBuilderFactory; +import org.apache.maven.model.building.ModelBuilder; +import org.apache.maven.model.building.ModelBuildingRequest; import org.dflib.jjava.jupyter.Extension; import org.dflib.jjava.jupyter.ExtensionLoader; import org.dflib.jjava.jupyter.kernel.magic.registry.CellMagic; import org.dflib.jjava.jupyter.kernel.magic.registry.LineMagic; import org.dflib.jjava.jupyter.kernel.magic.registry.MagicsArgs; import org.dflib.jjava.magics.dependencies.CommonRepositories; -import org.dflib.jjava.magics.dependencies.Maven; -import org.dflib.jjava.magics.dependencies.MavenToIvy; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -74,59 +72,42 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; public class MavenResolver { - private static final String DEFAULT_RESOLVER_NAME = "default"; - /** - * "master" includes the artifact published by the module. - * "runtime" includes the dependencies required for the module to run and - * extends "compile" which is the dependencies required to compile the module. - */ - private static final String[] DEFAULT_RESOLVE_CONFS = { "master", "runtime" }; - - /** - * The ivy artifact type corresponding to a binary artifact for a module. - */ - private static final String JAR_TYPE = "jar"; + private static final String DEFAULT_RESOLVER_NAME = "default"; /** - * The ivy artifact type corresponding to a jar artifact with an OSGi meta info, that we just ignore + * Ivy artifact coordinates in the form organization#name[#branch];revision. */ - private static final String BUNDLE_TYPE = "bundle"; - private static final Pattern IVY_MRID_PATTERN = Pattern.compile( "^(?[-\\w/._+=]*)#(?[-\\w/._+=]+)(?:#(?[-\\w/._+=]+))?;(?[-\\w/._+=,\\[\\]{}():@]+)$" ); - private static final Pattern MAVEN_MRID_PATTERN = Pattern.compile( - "^(?[^:\\s]+):(?[^:\\s]+)(?::(?[^:\\s]*)(?::(?[^:\\s]+))?)?:(?[^:\\s]+)$" - ); private final Consumer classPathHandler; private final Consumer extensionHandler; private final ExtensionLoader extensionLoader; - private final List repos; + private final List repositories; + private final Runtime runtime; public MavenResolver(Consumer classPathHandler, Consumer extensionHandler) { this.classPathHandler = classPathHandler; - this.extensionLoader = new ExtensionLoader(); this.extensionHandler = extensionHandler; - this.repos = new LinkedList<>(); - this.repos.add(CommonRepositories.mavenCentral()); - this.repos.add(CommonRepositories.mavenLocal()); + extensionLoader = new ExtensionLoader(); + repositories = new ArrayList<>(); + runtime = Runtimes.INSTANCE.getRuntime(); + + repositories.add(CommonRepositories.mavenCentral()); } /** @@ -139,194 +120,67 @@ public void initImplicitExtensions() { } public void addRemoteRepo(String name, String url) { - if (DEFAULT_RESOLVER_NAME.equals(name)) + if (DEFAULT_RESOLVER_NAME.equals(name)) { throw new IllegalArgumentException("Illegal repository name, cannot use '" + DEFAULT_RESOLVER_NAME + "'."); - - this.repos.add(CommonRepositories.maven(name, url)); - } - - private ChainResolver searchAllReposResolver(Set repos) { - ChainResolver resolver = new ChainResolver(); - resolver.setName(DEFAULT_RESOLVER_NAME); - - this.repos.stream() - .filter(r -> repos == null || repos.contains(r.getName().toLowerCase())) - .forEach(resolver::add); - - if (repos != null) { - Set resolverNames = resolver.getResolvers().stream() - .map(d -> d.getName().toLowerCase()) - .collect(Collectors.toSet()); - repos.removeAll(resolverNames); - - repos.forEach(r -> { - try { - URL url = new URL(r); - resolver.add(CommonRepositories.maven("from-" + url.getHost(), r)); - } catch (MalformedURLException e) { - // Ignore as we will assume that a bad url was a name - } - }); - } - - return resolver; - } - - private static ModuleRevisionId parseCanonicalArtifactName(String canonical) { - Matcher m = IVY_MRID_PATTERN.matcher(canonical); - if (m.matches()) { - return ModuleRevisionId.newInstance( - m.group("organization"), - m.group("name"), - m.group("branch"), - m.group("revision") - ); - } - - m = MAVEN_MRID_PATTERN.matcher(canonical); - if (m.matches()) { - String packaging = m.group("packaging"); - String classifier = m.group("classifier"); - - return ModuleRevisionId.newInstance( - m.group("group"), - m.group("artifact"), - m.group("version"), - packaging == null - ? Map.of() - : classifier == null - ? Map.of("ext", packaging) - : Map.of("ext", packaging, "m:classifier", classifier) - ); } - - throw new IllegalArgumentException("Cannot resolve '" + canonical + "' as maven or ivy coordinates."); - } - - /** - * Create an ivy instance with the specified verbosity. The instance is relatively plain. - * - * @param verbosity the verbosity level. - *
    - *
  1. ERROR
  2. - *
  3. WANRING
  4. - *
  5. INFO
  6. - *
  7. VERBOSE
  8. - *
  9. DEBUG
  10. - *
- * - * @return the fresh ivy instance. - */ - private Ivy createDefaultIvyInstance(int verbosity) { - MessageLogger logger = new DefaultMessageLogger(verbosity); - - // Set the default logger since not all things log to the ivy instance. - Message.setDefaultLogger(logger); - Ivy ivy = new Ivy(); - - ivy.getLoggerEngine().setDefaultLogger(logger); - ivy.setSettings(new IvySettings()); - ivy.bind(); - - return ivy; + repositories.add(CommonRepositories.maven(name, url)); } // TODO support multiple at once. This is necessary for conflict resolution with multiple overlapping dependencies. // TODO support classpath resolution - public List resolveMavenDependency(String canonical, Set repos, int verbosity) throws IOException, ParseException { - ChainResolver rootResolver = this.searchAllReposResolver(repos); - - Ivy ivy = this.createDefaultIvyInstance(verbosity); - IvySettings settings = ivy.getSettings(); - - settings.addResolver(rootResolver); - rootResolver.setCheckmodified(true); - settings.setDefaultResolver(rootResolver.getName()); - - ivy.getLoggerEngine().info("Searching for dependencies in: " + rootResolver.getResolvers()); - - ResolveOptions resolveOptions = new ResolveOptions(); - resolveOptions.setTransitive(true); - resolveOptions.setDownload(true); - - ModuleRevisionId artifactIdentifier = MavenResolver.parseCanonicalArtifactName(canonical); - DefaultModuleDescriptor containerModule = DefaultModuleDescriptor.newCallerInstance( - artifactIdentifier, - DEFAULT_RESOLVE_CONFS, - true, // Transitive - repos != null // Changing - the resolver will set this based on SNAPSHOT since they are all m2 compatible - // but if `repos` is specified, we want to force a lookup. - ); - - ResolveReport resolved = ivy.resolve(containerModule, resolveOptions); - if (resolved.hasError()) { - MessageLogger logger = ivy.getLoggerEngine(); - Arrays.stream(resolved.getAllArtifactsReports()) - .forEach(r -> { - logger.error("download " + r.getDownloadStatus() + ": " + r.getArtifact() + " of " + r.getType()); - if (r.getArtifactOrigin() == null) - logger.error("\tCouldn't find artifact."); - else - logger.error("\tfrom: " + r.getArtifactOrigin()); - }); - - // TODO better error... - throw new RuntimeException("Error resolving '" + canonical + "'. " + resolved.getAllProblemMessages()); - } - - return Arrays.stream(resolved.getAllArtifactsReports()) - .filter(a -> JAR_TYPE.equalsIgnoreCase(a.getType()) - || BUNDLE_TYPE.equalsIgnoreCase(a.getType())) - .map(ArtifactDownloadReport::getLocalFile) - .collect(Collectors.toList()); - } - - private File convertPomToIvy(Ivy ivy, File pomFile) throws IOException, ParseException { - PomModuleDescriptorParser parser = PomModuleDescriptorParser.getInstance(); + public List resolveMavenDependency(String coordinates, List repositories) throws Exception { + ContextOverrides overrides = ContextOverrides.create() + .withUserSettings(true) + .repositories(repositories.isEmpty() ? this.repositories : repositories) + .build(); - URL pomUrl = pomFile.toURI().toURL(); + try (Context context = runtime.create(overrides)) { + Artifact artifact = parseArtifact(coordinates); + Dependency dependency = new Dependency(artifact, "runtime"); - ModuleDescriptor pomModule = parser.parseDescriptor(new IvySettings(), pomFile.toURI().toURL(), false); + CollectRequest collectRequest = new CollectRequest(); + collectRequest.setRoot(dependency); + collectRequest.setRepositories(context.remoteRepositories()); - File tempIvyFile = File.createTempFile("jjava-ivy-", ".xml").getAbsoluteFile(); - tempIvyFile.deleteOnExit(); + DependencyRequest dependencyRequest = new DependencyRequest(); + dependencyRequest.setCollectRequest(collectRequest); - parser.toIvyFile(pomUrl.openStream(), new URLResource(pomUrl), tempIvyFile, pomModule); + RepositorySystemSession session = context.repositorySystemSession(); + DependencyNode rootNode = context.repositorySystem() + .resolveDependencies(session, dependencyRequest) + .getRoot(); - MessageLogger logger = ivy.getLoggerEngine(); - logger.info(Files.readString(tempIvyFile.toPath(), StandardCharsets.UTF_8)); + PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); + rootNode.accept(nlg); - return tempIvyFile; + String classpath = nlg.getClassPath(); + return Arrays.stream(classpath.split(File.pathSeparator)) + .map(File::new) + .collect(Collectors.toList()); + } } - private void addPomReposToIvySettings(IvySettings settings, File pomFile) throws ModelBuildingException { - Model mavenModel = Maven.getInstance().readEffectiveModel(pomFile).getEffectiveModel(); - ChainResolver pomRepos = MavenToIvy.createChainForModelRepositories(mavenModel); - pomRepos.setName(DEFAULT_RESOLVER_NAME); - - settings.addResolver(pomRepos); - settings.setDefaultResolver(DEFAULT_RESOLVER_NAME); + public void addJarsToClasspath(Iterable resolvedJars) { + resolvedJars.forEach(classPathHandler); + loadExtensions(resolvedJars); } - private List resolveFromIvyFile(Ivy ivy, File ivyFile, List scopes) throws IOException, ParseException { - ResolveOptions resolveOptions = new ResolveOptions(); - resolveOptions.setTransitive(true); - resolveOptions.setDownload(true); - resolveOptions.setConfs(!scopes.isEmpty() - ? scopes.toArray(new String[0]) - : DEFAULT_RESOLVE_CONFS - ); - - ResolveReport resolved = ivy.resolve(ivyFile, resolveOptions); - if (resolved.hasError()) - // TODO better error... - throw new RuntimeException("Error resolving '" + ivyFile + "'. " + resolved.getAllProblemMessages()); - - return Arrays.stream(resolved.getAllArtifactsReports()) - .map(ArtifactDownloadReport::getLocalFile) - .collect(Collectors.toList()); + public void loadExtensions(Iterable resolvedJars) { + extensionLoader + .loadExtensions(resolvedJars) + .forEach(extensionHandler); } + private static Artifact parseArtifact(String coordinates) { + Matcher ivyMatcher = IVY_MRID_PATTERN.matcher(coordinates); + if (ivyMatcher.matches()) { + String organization = ivyMatcher.group("organization"); + String name = ivyMatcher.group("name"); + String revision = ivyMatcher.group("revision"); + return new DefaultArtifact(organization, name, "jar", revision); + } + return new DefaultArtifact(coordinates); + } private String solidifyPartialPOM(String rawIn) throws ParserConfigurationException, IOException, SAXException, TransformerException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -346,7 +200,7 @@ private String solidifyPartialPOM(String rawIn) throws ParserConfigurationExcept // If input was a single "project" tag then we don't touch it. It is assumed // to be complete. if (rootChildren.getLength() == 1 && "project".equalsIgnoreCase(rootChildren.item(0).getNodeName())) - return this.writeDOM(new DOMSource(rootChildren.item(0))); + return writeDOM(new DOMSource(rootChildren.item(0))); // Put the pieces together and fill in the blanks. Document fixed = builder.newDocument(); @@ -367,40 +221,40 @@ private String solidifyPartialPOM(String rawIn) throws ParserConfigurationExcept switch (child.getNodeName()) { case "modelVersion": setModelVersion = true; - this.appendChildInNewDoc(child, fixed, project); + appendChildInNewDoc(child, fixed, project); break; case "groupId": setGroupId = true; - this.appendChildInNewDoc(child, fixed, project); + appendChildInNewDoc(child, fixed, project); break; case "artifactId": setArtifactId = true; - this.appendChildInNewDoc(child, fixed, project); + appendChildInNewDoc(child, fixed, project); break; case "version": setVersion = true; - this.appendChildInNewDoc(child, fixed, project); + appendChildInNewDoc(child, fixed, project); break; case "dependency": - this.appendChildInNewDoc(child, fixed, dependencies); + appendChildInNewDoc(child, fixed, dependencies); break; case "repository": - this.appendChildInNewDoc(child, fixed, repositories); + appendChildInNewDoc(child, fixed, repositories); break; case "dependencies": // Add all dependencies to the collecting tag NodeList dependencyChildren = child.getChildNodes(); for (int j = 0; j < dependencyChildren.getLength(); j++) - this.appendChildInNewDoc(dependencyChildren.item(j), fixed, dependencies); + appendChildInNewDoc(dependencyChildren.item(j), fixed, dependencies); break; case "repositories": // Add all repositories to the collecting tag NodeList repositoryChildren = child.getChildNodes(); for (int j = 0; j < repositoryChildren.getLength(); j++) - this.appendChildInNewDoc(repositoryChildren.item(j), fixed, repositories); + appendChildInNewDoc(repositoryChildren.item(j), fixed, repositories); break; default: - this.appendChildInNewDoc(child, fixed, project); + appendChildInNewDoc(child, fixed, project); break; } } @@ -425,7 +279,7 @@ private String solidifyPartialPOM(String rawIn) throws ParserConfigurationExcept version.setTextContent("1"); } - return this.writeDOM(new DOMSource(fixed)); + return writeDOM(new DOMSource(fixed)); } private void appendChildInNewDoc(Node oldNode, Document doc, Node newParent) { @@ -446,48 +300,43 @@ private String writeDOM(Source src) throws TransformerException { return out.toString(StandardCharsets.UTF_8); } - public void addJarsToClasspath(Iterable resolvedJars) { - resolvedJars.forEach(classPathHandler); - loadExtensions(resolvedJars); - } - - public void loadExtensions(Iterable resolvedJars) { - extensionLoader - .loadExtensions(resolvedJars) - .forEach(extensionHandler); - } - - @LineMagic(aliases = { "addMavenDependency", "maven" }) + @LineMagic(aliases = {"addMavenDependency", "maven"}) public void addMavenDependencies(List args) { MagicsArgs schema = MagicsArgs.builder() .varargs("deps") .keyword("from") - .flag("verbose", 'v') .onlyKnownKeywords() .onlyKnownFlags() .build(); Map> vals = schema.parse(args); - List deps = vals.get("deps"); List from = vals.get("from"); - int verbosity = vals.get("verbose").size(); - Set repos = from.isEmpty() ? null : new LinkedHashSet<>(from); + List repositoriesFrom = new ArrayList<>(); + for (String urlString : from) { + try { + URL url = new URL(urlString); + repositoriesFrom.add(CommonRepositories.maven("from-" + url.getHost(), url.toString())); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } for (String dep : deps) { try { - List resolvedJars = this.resolveMavenDependency(dep, repos, verbosity).stream() + List resolvedJars = resolveMavenDependency(dep, repositoriesFrom).stream() .map(File::getAbsolutePath) .collect(Collectors.toList()); addJarsToClasspath(resolvedJars); - } catch (IOException | ParseException e) { - throw new RuntimeException(e); + } catch (Exception e) { + System.out.println(e); + throw new RuntimeException("Failed to resolve dependency: " + dep, e); } } } - @LineMagic(aliases = { "mavenRepo" }) + @LineMagic(aliases = {"mavenRepo"}) public void addMavenRepo(List args) { MagicsArgs schema = MagicsArgs.builder().required("id").required("url").build(); Map> vals = schema.parse(args); @@ -495,7 +344,7 @@ public void addMavenRepo(List args) { String id = vals.get("id").get(0); String url = vals.get("url").get(0); - this.addRemoteRepo(id, url); + addRemoteRepo(id, url); } @CellMagic @@ -504,14 +353,14 @@ public void loadFromPOM(List args, String body) throws Exception { File tempPomPath = File.createTempFile("jjava-maven-", ".pom").getAbsoluteFile(); tempPomPath.deleteOnExit(); - String rawPom = this.solidifyPartialPOM(body); + String rawPom = solidifyPartialPOM(body); Files.write(tempPomPath.toPath(), rawPom.getBytes(StandardCharsets.UTF_8)); List loadArgs = new ArrayList<>(args.size() + 1); loadArgs.add(tempPomPath.getAbsolutePath()); loadArgs.addAll(args); - this.loadFromPOM(loadArgs); + loadFromPOM(loadArgs); } catch (IOException e) { throw new RuntimeException(e); } @@ -519,36 +368,45 @@ public void loadFromPOM(List args, String body) throws Exception { @LineMagic public void loadFromPOM(List args) { - if (args.isEmpty()) + if (args.isEmpty()) { throw new IllegalArgumentException("Loading from POM requires at least the path to the POM file"); + } MagicsArgs schema = MagicsArgs.builder() .required("pomPath") - .varargs("scopes") - .flag("verbose", 'v') .onlyKnownKeywords().onlyKnownFlags().build(); Map> vals = schema.parse(args); String pomPath = vals.get("pomPath").get(0); - List scopes = vals.get("scopes"); - int verbosity = vals.get("verbose").size(); File pomFile = new File(pomPath); + ModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance(); + org.apache.maven.model.building.Result modelResult = null; try { - Ivy ivy = this.createDefaultIvyInstance(verbosity); - IvySettings settings = ivy.getSettings(); - - File ivyFile = this.convertPomToIvy(ivy, pomFile); - - this.addPomReposToIvySettings(settings, pomFile); - - List resolvedJars = resolveFromIvyFile(ivy, ivyFile, scopes).stream() - .map(File::getAbsolutePath) + modelResult = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_STRICT, true); + Model model = modelResult.get(); + List pomRepositories = model.getRepositories().stream() + .map(repo -> new RemoteRepository.Builder(repo.getId(), repo.getName(), repo.getUrl()).build()) .collect(Collectors.toList()); - addJarsToClasspath(resolvedJars); - } catch (IOException | ParseException | ModelBuildingException e) { - throw new RuntimeException(e); + for (org.apache.maven.model.Dependency dep : model.getDependencies()) { + String coordinates = dep.getManagementKey() + ":" + dep.getVersion(); + try { + List resolvedJars = resolveMavenDependency(coordinates, pomRepositories).stream() + .map(File::getAbsolutePath) + .collect(Collectors.toList()); + addJarsToClasspath(resolvedJars); + } catch (Exception e) { + throw new RuntimeException("Failed to resolve dependency: " + dep.getManagementKey(), e); + } + } + } catch (Exception e) { + String message = "Failed to process POM file: " + pomPath; + if (modelResult != null) + message += "\n" + StreamSupport.stream(modelResult.getProblems().spliterator(), false) + .map(String::valueOf) + .collect(Collectors.joining("\n")); + throw new RuntimeException(message, e); } } } diff --git a/jjava/src/main/java/org/dflib/jjava/magics/dependencies/CommonRepositories.java b/jjava/src/main/java/org/dflib/jjava/magics/dependencies/CommonRepositories.java index e4cc7ab..e3b284e 100644 --- a/jjava/src/main/java/org/dflib/jjava/magics/dependencies/CommonRepositories.java +++ b/jjava/src/main/java/org/dflib/jjava/magics/dependencies/CommonRepositories.java @@ -23,57 +23,17 @@ */ package org.dflib.jjava.magics.dependencies; -import org.apache.ivy.plugins.resolver.DependencyResolver; -import org.apache.ivy.plugins.resolver.IBiblioResolver; -import org.xml.sax.SAXException; - -import java.io.IOException; -import java.nio.file.Path; +import org.eclipse.aether.repository.RemoteRepository; public class CommonRepositories { - protected static final String MAVEN_PATTERN_PREFIX = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])"; - protected static final String MAVEN_ARTIFACT_PATTERN = MAVEN_PATTERN_PREFIX + ".[ext]"; - protected static final String MAVEN_POM_PATTERN = MAVEN_PATTERN_PREFIX + ".pom"; - - public static DependencyResolver maven(String name, String urlRaw) { - IBiblioResolver resolver = new IBiblioResolver(); - resolver.setM2compatible(true); - resolver.setUseMavenMetadata(true); - resolver.setUsepoms(true); - - resolver.setRoot(urlRaw); - resolver.setName(name); - - return resolver; - } - public static DependencyResolver mavenCentral() { - return CommonRepositories.maven("maven-central", "https://repo.maven.apache.org/maven2/"); - } + private static final String MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2/"; - public static DependencyResolver jcenter() { - return CommonRepositories.maven("jcenter", "https://jcenter.bintray.com/"); + public static RemoteRepository maven(String id, String url) { + return new RemoteRepository.Builder(id, "default", url).build(); } - public static DependencyResolver mavenLocal() { - IBiblioResolver resolver = new IBiblioResolver(); - resolver.setM2compatible(true); - resolver.setUseMavenMetadata(true); - resolver.setUsepoms(true); - - resolver.setName("maven-local"); - - Path localRepoPath; - try { - localRepoPath = Maven.getInstance().getConfiguredLocalRepositoryPath(); - } catch (IOException e) { - throw new RuntimeException("Error reading maven settings. " + e.getLocalizedMessage(), e); - } catch (SAXException e) { - throw new RuntimeException("Error parsing maven settings. " + e.getLocalizedMessage(), e); - } - - resolver.setRoot("file:///" + localRepoPath.toString()); - - return resolver; + public static RemoteRepository mavenCentral() { + return maven("central", MAVEN_CENTRAL_URL); } } diff --git a/jjava/src/main/java/org/dflib/jjava/magics/dependencies/Maven.java b/jjava/src/main/java/org/dflib/jjava/magics/dependencies/Maven.java deleted file mode 100644 index 4751e16..0000000 --- a/jjava/src/main/java/org/dflib/jjava/magics/dependencies/Maven.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018 Spencer Park - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.dflib.jjava.magics.dependencies; - -import org.apache.maven.building.StringSource; -import org.apache.maven.model.building.DefaultModelBuilder; -import org.apache.maven.model.building.DefaultModelBuilderFactory; -import org.apache.maven.model.building.DefaultModelBuildingRequest; -import org.apache.maven.model.building.ModelBuildingException; -import org.apache.maven.model.building.ModelBuildingRequest; -import org.apache.maven.model.building.ModelBuildingResult; -import org.apache.maven.model.building.ModelSource; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.Map; -import java.util.Properties; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Maven { - private static final Pattern MAVEN_VAR_PATTERN = Pattern.compile("\\$\\{(?[^}*])}"); - - private static final Maven INSTANCE = new Maven(new Properties(), Collections.emptyMap()); - - public static Maven getInstance() { - return INSTANCE; - } - - // User provider environment overrides. - private final Properties properties; - private final Map environment; - - public Maven(Properties properties, Map environment) { - this.properties = properties; - this.environment = environment; - } - - private String getProperty(String name, String def) { - String val = this.environment.get(name); - if (val != null) - return val; - - val = System.getProperty(name); - return val != null ? val : def; - } - - private String getProperty(String name) { - return this.getProperty(name, null); - } - - private String getEnv(String name, String def) { - String val = this.environment.get(name); - if (val != null) - return val; - - val = System.getenv(name); - return val != null ? val : def; - } - - private String getEnv(String name) { - return this.getEnv(name, null); - } - - public Path getUserSystemHomePath() { - String home = this.getProperty("user.home"); - return Paths.get(home).toAbsolutePath(); - } - - private String replaceMavenVars(String raw) { - StringBuilder replaced = new StringBuilder(); - - Matcher matcher = MAVEN_VAR_PATTERN.matcher(raw); - while (matcher.find()) - matcher.appendReplacement(replaced, - System.getProperty(matcher.group("name"), "")); - - matcher.appendTail(replaced); - - return replaced.toString(); - } - - // Thanks gradle! - - private Path getUserHomePath() { - return this.getUserSystemHomePath().resolve(".m2"); - } - - private Path getGlobalHomePath() { - String envM2Home = this.getEnv("M2_HOME"); - return envM2Home != null - ? Paths.get(envM2Home).toAbsolutePath() : null; - } - - private Path getUserSettingsPath() { - return this.getUserHomePath().resolve("settings.xml"); - } - - private Path getGlobalSettingsPath() { - Path sysHome = this.getGlobalHomePath(); - return sysHome != null ? sysHome.resolve("conf").resolve("settings.xml") : null; - } - - private Path getDefaultLocalRepoPath() { - return this.getUserHomePath().resolve("repository"); - } - - private Path readConfiguredLocalRepositoryPath(Path settingsXmlPath) throws IOException, SAXException { - if (!Files.isRegularFile(settingsXmlPath)) - return null; - - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setValidating(false); - - DocumentBuilder builder; - try { - builder = factory.newDocumentBuilder(); - } catch (ParserConfigurationException e) { - // We are configuring the factory, the configuration will be fine... - e.printStackTrace(); - return null; - } - - try (InputStream in = Files.newInputStream(settingsXmlPath)) { - Document settingsDoc = builder.parse(in); - NodeList settings = settingsDoc.getElementsByTagName("settings"); - if (settings.getLength() == 0) - return null; - - for (int i = 0; i < settings.getLength(); i++) { - Node setting = settings.item(i); - switch (setting.getNodeName()) { - case "localRepository": - String localRepository = setting.getTextContent(); - localRepository = this.replaceMavenVars(localRepository); - return Paths.get(localRepository); - } - } - } - - return null; - } - - // TODO just use the effective settings - public Path getConfiguredLocalRepositoryPath() throws IOException, SAXException { - Path userSettingsXmlPath = this.getUserSettingsPath(); - Path path = this.readConfiguredLocalRepositoryPath(userSettingsXmlPath); - - if (path == null) { - Path globalSettingsXmlPath = this.getGlobalSettingsPath(); - if (globalSettingsXmlPath != null) - path = this.readConfiguredLocalRepositoryPath(globalSettingsXmlPath); - } - - return path == null ? this.getDefaultLocalRepoPath() : path; - } - - public ModelBuildingResult readEffectiveModel(CharSequence pom) throws ModelBuildingException { - return this.readEffectiveModel(req -> - req.setModelSource((ModelSource) new StringSource(pom)) - ); - } - - public ModelBuildingResult readEffectiveModel(File pom) throws ModelBuildingException { - return this.readEffectiveModel(req -> - req.setPomFile(pom) - ); - } - - private ModelBuildingResult readEffectiveModel(Function configuration) throws ModelBuildingException { - DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance(); - - ModelBuildingRequest request = new DefaultModelBuildingRequest(); - - request.setSystemProperties(System.getProperties()); - request.setUserProperties(this.properties); - - // Allow force selection of active profile - // request.setActiveProfileIds() - // request.setInactiveProfileIds() - - // Better error messages for bad poms - request.setLocationTracking(true); - - // Don't run plugins, in most cases this is what we want. I don't know of any - // that would affect the POM. - request.setProcessPlugins(false); - - request = configuration.apply(request); - - return modelBuilder.build(request); - } -} diff --git a/jjava/src/main/java/org/dflib/jjava/magics/dependencies/MavenToIvy.java b/jjava/src/main/java/org/dflib/jjava/magics/dependencies/MavenToIvy.java deleted file mode 100644 index 78e0aee..0000000 --- a/jjava/src/main/java/org/dflib/jjava/magics/dependencies/MavenToIvy.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018 Spencer Park - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.dflib.jjava.magics.dependencies; - -import org.apache.ivy.plugins.resolver.ChainResolver; -import org.apache.ivy.plugins.resolver.DependencyResolver; -import org.apache.maven.model.Model; -import org.apache.maven.model.Repository; -import org.apache.maven.model.building.ModelBuildingException; - -import java.io.File; -import java.util.List; -import java.util.stream.Collectors; - -public class MavenToIvy { - public static List getRepositoriesFromModel(CharSequence pom) throws ModelBuildingException { - return getRepositoriesFromModel(Maven.getInstance().readEffectiveModel(pom).getEffectiveModel()); - } - - public static List getRepositoriesFromModel(File pom) throws ModelBuildingException { - return getRepositoriesFromModel(Maven.getInstance().readEffectiveModel(pom).getEffectiveModel()); - } - - public static List getRepositoriesFromModel(Model model) { - return model.getRepositories().stream() - .map(MavenToIvy::convertRepository) - .collect(Collectors.toList()); - } - - public static DependencyResolver convertRepository(Repository repository) { - return CommonRepositories.maven(repository.getId(), repository.getUrl()); - } - - public static ChainResolver createChainForModelRepositories(Model model) { - ChainResolver resolver = new ChainResolver(); - - // Maven central is always an implicit repository. - resolver.add(CommonRepositories.mavenCentral()); - - MavenToIvy.getRepositoriesFromModel(model).forEach(resolver::add); - - return resolver; - } -} diff --git a/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java b/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java index 2bf71a6..666ae20 100644 --- a/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java +++ b/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java @@ -45,7 +45,7 @@ void classpath() throws Exception { } @Test - void addMavenDependencies() throws Exception { + void addMavenDependency() throws Exception { String snippet = String.join("\n", "%maven org.dflib:dflib-jupyter:1.0.0-RC1", "System.getProperty(\"java.class.path\")" @@ -56,6 +56,19 @@ void addMavenDependencies() throws Exception { assertThat(snippetResult.getStdout(), containsString("dflib-jupyter-1.0.0-RC1.jar")); } + @Test + void addIvyDependency() throws Exception { + String snippet = String.join("\n", + "%maven jakarta.annotation#jakarta.annotation-api;3.0.0", + "System.getProperty(\"java.class.path\")" + ); + + Container.ExecResult snippetResult = executeInKernel(snippet); + + assertThat(snippetResult.getStderr(), not(containsString("|"))); + assertThat(snippetResult.getStdout(), containsString("jakarta.annotation-api-3.0.0.jar")); + } + @Test void load() throws Exception { String script = CONTAINER_RESOURCES + "/test-ping.jshell"; diff --git a/pom.xml b/pom.xml index 9b4e057..440b07d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 org.dflib.jjava @@ -44,8 +45,8 @@ UTF-8 11 - 2.5.2 - 3.6.0 + 3.9.9 + 2.4.0 2.11.0 0.6.0 @@ -87,14 +88,29 @@ ${gson.version}
- org.apache.ivy - ivy - ${ivy.version} + eu.maveniverse.maven.mima + context + ${maven.mima.version} + + + eu.maveniverse.maven.mima.runtime + standalone-static + ${maven.mima.version} + + + eu.maveniverse.maven.mima.extensions + maven-model-reader + ${maven.mima.version} org.apache.maven maven-model-builder - ${maven.model.builder.version} + ${maven.version} + + + org.apache.maven + maven-model + ${maven.version} org.zeromq