001/* 002 * JDrupes Builder 003 * Copyright (C) 2025 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.mvnrepo; 020 021import eu.maveniverse.maven.mima.context.Context; 022import eu.maveniverse.maven.mima.context.ContextOverrides; 023import eu.maveniverse.maven.mima.context.Runtime; 024import eu.maveniverse.maven.mima.context.Runtimes; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.List; 028import java.util.stream.Stream; 029import org.eclipse.aether.RepositorySystem; 030import org.eclipse.aether.RepositorySystemSession; 031import org.eclipse.aether.artifact.Artifact; 032import org.eclipse.aether.artifact.DefaultArtifact; 033import org.eclipse.aether.collection.CollectRequest; 034import org.eclipse.aether.graph.Dependency; 035import org.eclipse.aether.graph.DependencyNode; 036import org.eclipse.aether.resolution.ArtifactRequest; 037import org.eclipse.aether.resolution.ArtifactResolutionException; 038import org.eclipse.aether.resolution.DependencyRequest; 039import org.eclipse.aether.resolution.DependencyResolutionException; 040import org.eclipse.aether.util.artifact.SubArtifact; 041import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; 042import org.jdrupes.builder.api.BuildException; 043import org.jdrupes.builder.api.Resource; 044import org.jdrupes.builder.api.ResourceFactory; 045import org.jdrupes.builder.api.ResourceProvider; 046import org.jdrupes.builder.api.ResourceRequest; 047import org.jdrupes.builder.core.AbstractProvider; 048import org.jdrupes.builder.java.CompilationResources; 049import static org.jdrupes.builder.java.JavaTypes.*; 050import org.jdrupes.builder.java.RuntimeResources; 051import static org.jdrupes.builder.mvnrepo.MvnRepoTypes.*; 052 053/// Depending on the request, this provider provides two types of resources. 054/// 055/// 1. The artifacts to be resolved as [MvnRepoDependencies]. The artifacts 056/// to be resolved are those added with [resolve]. 057/// 058/// 2. The [CompilationResources] or [RuntimeResources] (depending on the 059/// request) that result from resolving the artifacts to be resolved. 060/// 061public class MvnRepoLookup extends AbstractProvider 062 implements ResourceProvider { 063 064 private final List<String> coordinates = new ArrayList<>(); 065 private boolean downloadSources = true; 066 private boolean downloadJavadoc = true; 067 private static Context rootContextInstance; 068 069 /// Instantiates a new mvn repo lookup. 070 /// 071 @SuppressWarnings("PMD.UnnecessaryConstructor") 072 public MvnRepoLookup() { 073 // Make javadoc happy. 074 } 075 076 /// Lazily creates the root context. 077 /// @return the context 078 /// 079 /* default */ static Context rootContext() { 080 if (rootContextInstance != null) { 081 return rootContextInstance; 082 } 083 ContextOverrides overrides = ContextOverrides.create() 084 .withUserSettings(true).build(); 085 Runtime runtime = Runtimes.INSTANCE.getRuntime(); 086 rootContextInstance = runtime.create(overrides); 087 return rootContextInstance; 088 } 089 090 /// Add artifacts, specified by their coordinates 091 /// (`groupId:artifactId:version`). 092 /// 093 /// @param coordinates the coordinates 094 /// @return the mvn repo lookup 095 /// 096 public MvnRepoLookup resolve(String... coordinates) { 097 this.coordinates.addAll(Arrays.asList(coordinates)); 098 return this; 099 } 100 101 /// Whether to also download the sources. Defaults to `true`. 102 /// 103 /// @param enable the enable 104 /// @return the mvn repo lookup 105 /// 106 public MvnRepoLookup downloadSources(boolean enable) { 107 this.downloadSources = enable; 108 return this; 109 } 110 111 /// Whether to also download the javadoc. Defaults to `true`. 112 /// 113 /// @param enable the enable 114 /// @return the mvn repo lookup 115 /// 116 public MvnRepoLookup downloadJavadoc(boolean enable) { 117 this.downloadJavadoc = enable; 118 return this; 119 } 120 121 /// Provide. 122 /// 123 /// @param <T> the generic type 124 /// @param requested the requested resources 125 /// @return the stream 126 /// 127 @Override 128 protected <T extends Resource> Stream<T> 129 doProvide(ResourceRequest<T> requested) { 130 if (requested.wants(MvnRepoDependenciesType)) { 131 @SuppressWarnings("unchecked") 132 var result = (Stream<T>) coordinates.stream() 133 .map(c -> ResourceFactory.create(MvnRepoDependencyType, null, 134 c)); 135 return result; 136 } 137 if (requested.wants(CompilationResourcesType) 138 && requested.includes(JarFileType)) { 139 return provideJars(requested); 140 } 141 return Stream.empty(); 142 } 143 144 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 145 private <T extends Resource> Stream<T> 146 provideJars(ResourceRequest<T> requested) { 147 CollectRequest collectRequest = new CollectRequest() 148 .setRepositories(rootContext().remoteRepositories()); 149 for (var coord : coordinates) { 150 collectRequest.addDependency( 151 new Dependency(new DefaultArtifact(coord), 152 requested.wants(CompilationResourcesType) ? "compile" 153 : "runtime")); 154 } 155 156 DependencyRequest dependencyRequest 157 = new DependencyRequest(collectRequest, null); 158 DependencyNode rootNode; 159 try { 160 var repoSystem = rootContext().repositorySystem(); 161 var repoSession = rootContext().repositorySystemSession(); 162 rootNode = repoSystem.resolveDependencies(repoSession, 163 dependencyRequest).getRoot(); 164// For maven 2.x libraries: 165// List<DependencyNode> dependencyNodes = new ArrayList<>(); 166// rootNode.accept(new PreorderDependencyNodeConsumerVisitor( 167// dependencyNodes::add)); 168 PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); 169 rootNode.accept(nlg); 170 List<DependencyNode> dependencyNodes = nlg.getNodes(); 171 @SuppressWarnings("unchecked") 172 var result = (Stream<T>) dependencyNodes.stream() 173 .filter(d -> d.getArtifact() != null) 174 .map(DependencyNode::getArtifact) 175 .map(a -> { 176 if (downloadSources) { 177 downloadSourceJar(repoSystem, repoSession, a); 178 } 179 if (downloadJavadoc) { 180 downloadJavadocJar(repoSystem, repoSession, a); 181 } 182 return a; 183 }).map(a -> a.getFile().toPath()) 184 .map(p -> ResourceFactory.create(MvnRepoJarFileType, p)); 185 return result; 186 } catch (DependencyResolutionException e) { 187 throw new BuildException( 188 "Cannot resolve: " + e.getMessage(), e); 189 } 190 } 191 192 private void downloadSourceJar(RepositorySystem repoSystem, 193 RepositorySystemSession repoSession, Artifact jarArtifact) { 194 Artifact sourcesArtifact 195 = new SubArtifact(jarArtifact, "sources", "jar"); 196 ArtifactRequest sourcesRequest = new ArtifactRequest(); 197 sourcesRequest.setArtifact(sourcesArtifact); 198 sourcesRequest.setRepositories(rootContext().remoteRepositories()); 199 try { 200 repoSystem.resolveArtifact(repoSession, sourcesRequest); 201 } catch (ArtifactResolutionException e) { // NOPMD 202 // Ignore, sources are optional 203 } 204 } 205 206 private void downloadJavadocJar(RepositorySystem repoSystem, 207 RepositorySystemSession repoSession, Artifact jarArtifact) { 208 Artifact javadocArtifact 209 = new SubArtifact(jarArtifact, "javadoc", "jar"); 210 ArtifactRequest sourcesRequest = new ArtifactRequest(); 211 sourcesRequest.setArtifact(javadocArtifact); 212 sourcesRequest.setRepositories(rootContext().remoteRepositories()); 213 try { 214 repoSystem.resolveArtifact(repoSession, sourcesRequest); 215 } catch (ArtifactResolutionException e) { // NOPMD 216 // Ignore, javadoc is optional 217 } 218 } 219}