1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.internal.transformation.impl;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23
24 import java.nio.file.Path;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.function.Function;
29 import java.util.stream.Collectors;
30
31 import org.apache.maven.api.ArtifactCoordinates;
32 import org.apache.maven.api.Node;
33 import org.apache.maven.api.PathScope;
34 import org.apache.maven.api.SessionData;
35 import org.apache.maven.api.model.Dependency;
36 import org.apache.maven.api.model.DistributionManagement;
37 import org.apache.maven.api.model.Model;
38 import org.apache.maven.api.model.ModelBase;
39 import org.apache.maven.api.model.Profile;
40 import org.apache.maven.api.model.Repository;
41 import org.apache.maven.api.model.Scm;
42 import org.apache.maven.api.services.ModelBuilder;
43 import org.apache.maven.api.services.ModelBuilderException;
44 import org.apache.maven.api.services.ModelBuilderRequest;
45 import org.apache.maven.api.services.ModelBuilderResult;
46 import org.apache.maven.api.services.Sources;
47 import org.apache.maven.api.services.model.LifecycleBindingsInjector;
48 import org.apache.maven.impl.InternalSession;
49 import org.apache.maven.model.v4.MavenModelVersion;
50 import org.apache.maven.project.MavenProject;
51 import org.eclipse.aether.RepositorySystemSession;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 @Named
56 class DefaultConsumerPomBuilder implements PomBuilder {
57 private static final String BOM_PACKAGING = "bom";
58
59 public static final String POM_PACKAGING = "pom";
60
61 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConsumerPomBuilder.class);
62
63 private final LifecycleBindingsInjector lifecycleBindingsInjector;
64
65 @Inject
66 @SuppressWarnings("checkstyle:ParameterNumber")
67 DefaultConsumerPomBuilder(LifecycleBindingsInjector lifecycleBindingsInjector) {
68 this.lifecycleBindingsInjector = lifecycleBindingsInjector;
69 }
70
71 @Override
72 public Model build(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException {
73 Model model = project.getModel().getDelegate();
74 String packaging = model.getPackaging();
75 String originalPackaging = project.getOriginalModel().getPackaging();
76 if (POM_PACKAGING.equals(packaging)) {
77 if (BOM_PACKAGING.equals(originalPackaging)) {
78 return buildBom(session, project, src);
79 } else {
80 return buildPom(session, project, src);
81 }
82 } else {
83 return buildNonPom(session, project, src);
84 }
85 }
86
87 protected Model buildPom(RepositorySystemSession session, MavenProject project, Path src)
88 throws ModelBuilderException {
89 ModelBuilderResult result = buildModel(session, src);
90 Model model = result.getRawModel();
91 return transformPom(model, project);
92 }
93
94 protected Model buildBom(RepositorySystemSession session, MavenProject project, Path src)
95 throws ModelBuilderException {
96 ModelBuilderResult result = buildModel(session, src);
97 Model model = result.getEffectiveModel();
98 return transformBom(model, project);
99 }
100
101 protected Model buildNonPom(RepositorySystemSession session, MavenProject project, Path src)
102 throws ModelBuilderException {
103 Model model = buildEffectiveModel(session, src);
104 return transformNonPom(model, project);
105 }
106
107 private Model buildEffectiveModel(RepositorySystemSession session, Path src) throws ModelBuilderException {
108 InternalSession iSession = InternalSession.from(session);
109 ModelBuilderResult result = buildModel(session, src);
110 Model model = result.getEffectiveModel();
111
112 if (model.getDependencyManagement() != null
113 && !model.getDependencyManagement().getDependencies().isEmpty()) {
114 ArtifactCoordinates artifact = iSession.createArtifactCoordinates(
115 model.getGroupId(), model.getArtifactId(), model.getVersion(), null);
116 Node node = iSession.collectDependencies(
117 iSession.createDependencyCoordinates(artifact), PathScope.TEST_RUNTIME);
118
119 Map<String, Node> nodes = node.stream()
120 .collect(Collectors.toMap(n -> getDependencyKey(n.getDependency()), Function.identity()));
121 Map<String, Dependency> directDependencies = model.getDependencies().stream()
122 .filter(dependency -> !"import".equals(dependency.getScope()))
123 .collect(Collectors.toMap(
124 DefaultConsumerPomBuilder::getDependencyKey,
125 Function.identity(),
126 this::merge,
127 LinkedHashMap::new));
128 Map<String, Dependency> managedDependencies = model.getDependencyManagement().getDependencies().stream()
129 .filter(dependency ->
130 nodes.containsKey(getDependencyKey(dependency)) && !"import".equals(dependency.getScope()))
131 .collect(Collectors.toMap(
132 DefaultConsumerPomBuilder::getDependencyKey,
133 Function.identity(),
134 this::merge,
135 LinkedHashMap::new));
136
137
138
139
140
141 managedDependencies.keySet().retainAll(nodes.keySet());
142
143 directDependencies.replaceAll((key, dependency) -> {
144 var managedDependency = managedDependencies.get(key);
145 if (managedDependency != null) {
146 if (dependency.getVersion() == null && managedDependency.getVersion() != null) {
147 dependency = dependency.withVersion(managedDependency.getVersion());
148 }
149 if (dependency.getScope() == null && managedDependency.getScope() != null) {
150 dependency = dependency.withScope(managedDependency.getScope());
151 }
152 if (dependency.getOptional() == null && managedDependency.getOptional() != null) {
153 dependency = dependency.withOptional(managedDependency.getOptional());
154 }
155 if (dependency.getExclusions().isEmpty()
156 && !managedDependency.getExclusions().isEmpty()) {
157 dependency = dependency.withExclusions(managedDependency.getExclusions());
158 }
159 }
160 return dependency;
161 });
162 managedDependencies.keySet().removeAll(directDependencies.keySet());
163
164 model = model.withDependencyManagement(
165 managedDependencies.isEmpty()
166 ? null
167 : model.getDependencyManagement().withDependencies(managedDependencies.values()))
168 .withDependencies(directDependencies.isEmpty() ? null : directDependencies.values());
169 }
170
171 return model;
172 }
173
174 private Dependency merge(Dependency dep1, Dependency dep2) {
175 throw new IllegalArgumentException("Duplicate dependency: " + dep1);
176 }
177
178 private static String getDependencyKey(org.apache.maven.api.Dependency dependency) {
179 return dependency.getGroupId() + ":" + dependency.getArtifactId() + ":"
180 + dependency.getType().id() + ":" + dependency.getClassifier();
181 }
182
183 private static String getDependencyKey(Dependency dependency) {
184 return dependency.getGroupId() + ":" + dependency.getArtifactId() + ":"
185 + (dependency.getType() != null ? dependency.getType() : "") + ":"
186 + (dependency.getClassifier() != null ? dependency.getClassifier() : "");
187 }
188
189 private ModelBuilderResult buildModel(RepositorySystemSession session, Path src) throws ModelBuilderException {
190 InternalSession iSession = InternalSession.from(session);
191 ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder();
192 request.requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER);
193 request.session(iSession);
194 request.source(Sources.buildSource(src));
195 request.locationTracking(false);
196 request.systemProperties(session.getSystemProperties());
197 request.userProperties(session.getUserProperties());
198 request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings);
199 ModelBuilder.ModelBuilderSession mbSession =
200 iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class));
201 return mbSession.build(request.build());
202 }
203
204 static Model transformNonPom(Model model, MavenProject project) {
205 boolean preserveModelVersion = model.isPreserveModelVersion();
206
207 Model.Builder builder = prune(
208 Model.newBuilder(model, true)
209 .preserveModelVersion(false)
210 .root(false)
211 .parent(null)
212 .build(null),
213 model)
214 .mailingLists(null)
215 .issueManagement(null)
216 .scm(
217 model.getScm() != null
218 ? Scm.newBuilder(model.getScm(), true)
219 .childScmConnectionInheritAppendPath(null)
220 .childScmUrlInheritAppendPath(null)
221 .childScmDeveloperConnectionInheritAppendPath(null)
222 .build()
223 : null);
224 builder.profiles(prune(model.getProfiles()));
225
226 model = builder.build();
227 String modelVersion = new MavenModelVersion().getModelVersion(model);
228 if (!ModelBuilder.MODEL_VERSION_4_0_0.equals(modelVersion) && !preserveModelVersion) {
229 warnNotDowngraded(project);
230 }
231 model = model.withModelVersion(modelVersion);
232 return model;
233 }
234
235 static Model transformBom(Model model, MavenProject project) {
236 boolean preserveModelVersion = model.isPreserveModelVersion();
237
238 Model.Builder builder = prune(
239 Model.newBuilder(model, true)
240 .preserveModelVersion(false)
241 .root(false)
242 .parent(null)
243 .build(null),
244 model);
245 builder.packaging(POM_PACKAGING);
246 builder.profiles(prune(model.getProfiles()));
247
248 model = builder.build();
249 String modelVersion = new MavenModelVersion().getModelVersion(model);
250 if (!ModelBuilder.MODEL_VERSION_4_0_0.equals(modelVersion) && !preserveModelVersion) {
251 warnNotDowngraded(project);
252 }
253 model = model.withModelVersion(modelVersion);
254 return model;
255 }
256
257 static Model transformPom(Model model, MavenProject project) {
258 boolean preserveModelVersion = model.isPreserveModelVersion();
259
260
261 model = model.withRoot(false).withModules(null).withSubprojects(null);
262 if (model.getParent() != null) {
263 model = model.withParent(model.getParent().withRelativePath(null));
264 }
265
266 if (!preserveModelVersion) {
267 model = model.withPreserveModelVersion(false);
268 String modelVersion = new MavenModelVersion().getModelVersion(model);
269 model = model.withModelVersion(modelVersion);
270 }
271 return model;
272 }
273
274 static void warnNotDowngraded(MavenProject project) {
275 LOGGER.warn("The consumer POM for " + project.getId() + " cannot be downgraded to 4.0.0. "
276 + "If you intent your build to be consumed with Maven 3 projects, you need to remove "
277 + "the features that request a newer model version. If you're fine with having the "
278 + "consumer POM not consumable with Maven 3, add the `preserve.model.version='true'` "
279 + "attribute on the <project> element of your POM.");
280 }
281
282 private static List<Profile> prune(List<Profile> profiles) {
283 return profiles.stream()
284 .map(p -> {
285 Profile.Builder builder = Profile.newBuilder(p, true);
286 prune((ModelBase.Builder) builder, p);
287 return builder.build(null).build();
288 })
289 .filter(p -> !isEmpty(p))
290 .collect(Collectors.toList());
291 }
292
293 private static boolean isEmpty(Profile profile) {
294 return profile.getActivation() == null
295 && profile.getBuild() == null
296 && profile.getDependencies().isEmpty()
297 && (profile.getDependencyManagement() == null
298 || profile.getDependencyManagement().getDependencies().isEmpty())
299 && profile.getDistributionManagement() == null
300 && profile.getModules().isEmpty()
301 && profile.getSubprojects().isEmpty()
302 && profile.getProperties().isEmpty()
303 && profile.getRepositories().isEmpty()
304 && profile.getPluginRepositories().isEmpty()
305 && profile.getReporting() == null;
306 }
307
308 private static <T extends ModelBase.Builder> T prune(T builder, ModelBase model) {
309 builder.properties(null).reporting(null);
310 if (model.getDistributionManagement() != null
311 && model.getDistributionManagement().getRelocation() != null) {
312
313 builder.distributionManagement(DistributionManagement.newBuilder()
314 .relocation(model.getDistributionManagement().getRelocation())
315 .build());
316 }
317
318 builder.repositories(pruneRepositories(model.getRepositories()));
319 builder.pluginRepositories(null);
320 return builder;
321 }
322
323 private static List<Repository> pruneRepositories(List<Repository> repositories) {
324 return repositories.stream()
325 .filter(r -> !org.apache.maven.api.Repository.CENTRAL_ID.equals(r.getId()))
326 .collect(Collectors.toList());
327 }
328 }