1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.shared.release.phase;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.concurrent.atomic.AtomicReference;
35
36 import org.apache.maven.artifact.Artifact;
37 import org.apache.maven.artifact.ArtifactUtils;
38 import org.apache.maven.project.MavenProject;
39 import org.apache.maven.shared.release.ReleaseExecutionException;
40 import org.apache.maven.shared.release.ReleaseFailureException;
41 import org.apache.maven.shared.release.ReleaseResult;
42 import org.apache.maven.shared.release.config.ReleaseDescriptor;
43 import org.apache.maven.shared.release.env.ReleaseEnvironment;
44 import org.apache.maven.shared.release.versions.DefaultVersionInfo;
45 import org.apache.maven.shared.release.versions.VersionInfo;
46 import org.apache.maven.shared.release.versions.VersionParseException;
47 import org.codehaus.plexus.components.interactivity.Prompter;
48 import org.codehaus.plexus.components.interactivity.PrompterException;
49
50 import static java.util.Objects.requireNonNull;
51
52
53
54
55
56
57
58
59
60
61
62 @Singleton
63 @Named("check-dependency-snapshots")
64 public class CheckDependencySnapshotsPhase extends AbstractReleasePhase {
65 public static final String RESOLVE_SNAPSHOT_MESSAGE = "There are still some remaining snapshot dependencies.\n";
66
67 public static final String RESOLVE_SNAPSHOT_PROMPT = "Do you want to resolve them now?";
68
69 public static final String RESOLVE_SNAPSHOT_TYPE_MESSAGE = "Dependency type to resolve,";
70
71 public static final String RESOLVE_SNAPSHOT_TYPE_PROMPT =
72 "specify the selection number ( 0:All 1:Project Dependencies 2:Plugins 3:Reports 4:Extensions ):";
73
74
75
76
77 private final AtomicReference<Prompter> prompter;
78
79
80
81
82
83
84
85
86
87
88 private String resolveSnapshot;
89
90 private String resolveSnapshotType;
91
92 @Inject
93 public CheckDependencySnapshotsPhase(Prompter prompter) {
94 this.prompter = new AtomicReference<>(requireNonNull(prompter));
95 }
96
97
98
99
100 public void setPrompter(Prompter prompter) {
101 this.prompter.set(prompter);
102 }
103
104 @Override
105 public ReleaseResult execute(
106 ReleaseDescriptor releaseDescriptor,
107 ReleaseEnvironment releaseEnvironment,
108 List<MavenProject> reactorProjects)
109 throws ReleaseExecutionException, ReleaseFailureException {
110 ReleaseResult result = new ReleaseResult();
111
112 if (!releaseDescriptor.isAllowTimestampedSnapshots()) {
113 logInfo(result, "Checking dependencies and plugins for snapshots ...");
114
115 for (MavenProject project : reactorProjects) {
116 checkProject(project, releaseDescriptor);
117 }
118 } else {
119 logInfo(result, "Ignoring SNAPSHOT dependencies and plugins ...");
120 }
121 result.setResultCode(ReleaseResult.SUCCESS);
122
123 return result;
124 }
125
126 private void checkProject(MavenProject project, ReleaseDescriptor releaseDescriptor)
127 throws ReleaseFailureException, ReleaseExecutionException {
128 Map<String, Artifact> artifactMap = ArtifactUtils.artifactMapByVersionlessId(project.getArtifacts());
129
130 Set<Artifact> usedSnapshotDependencies = new HashSet<>();
131
132 if (project.getParentArtifact() != null) {
133 if (checkArtifact(project.getParentArtifact(), artifactMap, releaseDescriptor)) {
134 usedSnapshotDependencies.add(project.getParentArtifact());
135 }
136 }
137
138 Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
139 usedSnapshotDependencies.addAll(checkDependencies(releaseDescriptor, artifactMap, dependencyArtifacts));
140
141
142
143 Set<Artifact> pluginArtifacts = project.getPluginArtifacts();
144 Set<Artifact> usedSnapshotPlugins = checkPlugins(releaseDescriptor, artifactMap, pluginArtifacts);
145
146
147
148 Set<Artifact> reportArtifacts = project.getReportArtifacts();
149 Set<Artifact> usedSnapshotReports = checkReports(releaseDescriptor, artifactMap, reportArtifacts);
150
151 Set<Artifact> extensionArtifacts = project.getExtensionArtifacts();
152 Set<Artifact> usedSnapshotExtensions = checkExtensions(releaseDescriptor, artifactMap, extensionArtifacts);
153
154
155
156 if (!usedSnapshotDependencies.isEmpty()
157 || !usedSnapshotReports.isEmpty()
158 || !usedSnapshotExtensions.isEmpty()
159 || !usedSnapshotPlugins.isEmpty()) {
160 if (releaseDescriptor.isInteractive() || null != releaseDescriptor.getAutoResolveSnapshots()) {
161 resolveSnapshots(
162 usedSnapshotDependencies,
163 usedSnapshotReports,
164 usedSnapshotExtensions,
165 usedSnapshotPlugins,
166 releaseDescriptor);
167 }
168
169 if (!usedSnapshotDependencies.isEmpty()
170 || !usedSnapshotReports.isEmpty()
171 || !usedSnapshotExtensions.isEmpty()
172 || !usedSnapshotPlugins.isEmpty()) {
173 StringBuilder message = new StringBuilder();
174
175 printSnapshotDependencies(usedSnapshotDependencies, message);
176 printSnapshotDependencies(usedSnapshotReports, message);
177 printSnapshotDependencies(usedSnapshotExtensions, message);
178 printSnapshotDependencies(usedSnapshotPlugins, message);
179 message.append("in project '" + project.getName() + "' (" + project.getId() + ")");
180
181 throw new ReleaseFailureException(
182 "Can't release project due to non released dependencies :\n" + message);
183 }
184 }
185 }
186
187 private Set<Artifact> checkPlugins(
188 ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> pluginArtifacts)
189 throws ReleaseExecutionException {
190 Set<Artifact> usedSnapshotPlugins = new HashSet<>();
191 for (Artifact artifact : pluginArtifacts) {
192 if (checkArtifact(artifact, artifactMap, releaseDescriptor)) {
193 boolean addToFailures;
194
195 if ("org.apache.maven.plugins".equals(artifact.getGroupId())
196 && "maven-release-plugin".equals(artifact.getArtifactId())) {
197
198
199 if (releaseDescriptor.isSnapshotReleasePluginAllowed()) {
200 addToFailures = false;
201 } else if (releaseDescriptor.isInteractive()) {
202 try {
203 String result;
204 if (!releaseDescriptor.isSnapshotReleasePluginAllowed()) {
205 prompter.get()
206 .showMessage("This project relies on a SNAPSHOT of the release plugin. "
207 + "This may be necessary during testing.\n");
208 result = prompter.get()
209 .prompt(
210 "Do you want to continue with the release?",
211 Arrays.asList("yes", "no"),
212 "no");
213 } else {
214 result = "yes";
215 }
216
217 addToFailures = !result.toLowerCase(Locale.ENGLISH).startsWith("y");
218 } catch (PrompterException e) {
219 throw new ReleaseExecutionException(e.getMessage(), e);
220 }
221 } else {
222 addToFailures = true;
223 }
224 } else {
225 addToFailures = true;
226 }
227
228 if (addToFailures) {
229 usedSnapshotPlugins.add(artifact);
230 }
231 }
232 }
233 return usedSnapshotPlugins;
234 }
235
236 private Set<Artifact> checkDependencies(
237 ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> dependencyArtifacts) {
238 Set<Artifact> usedSnapshotDependencies = new HashSet<>();
239 for (Artifact artifact : dependencyArtifacts) {
240 if (checkArtifact(artifact, artifactMap, releaseDescriptor)) {
241 usedSnapshotDependencies.add(getArtifactFromMap(artifact, artifactMap));
242 }
243 }
244 return usedSnapshotDependencies;
245 }
246
247 private Set<Artifact> checkReports(
248 ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> reportArtifacts) {
249 Set<Artifact> usedSnapshotReports = new HashSet<>();
250 for (Artifact artifact : reportArtifacts) {
251 if (checkArtifact(artifact, artifactMap, releaseDescriptor)) {
252
253 usedSnapshotReports.add(artifact);
254 }
255 }
256 return usedSnapshotReports;
257 }
258
259 private Set<Artifact> checkExtensions(
260 ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> extensionArtifacts) {
261 Set<Artifact> usedSnapshotExtensions = new HashSet<>();
262 for (Artifact artifact : extensionArtifacts) {
263 if (checkArtifact(artifact, artifactMap, releaseDescriptor)) {
264 usedSnapshotExtensions.add(artifact);
265 }
266 }
267 return usedSnapshotExtensions;
268 }
269
270 private static boolean checkArtifact(
271 Artifact artifact, Map<String, Artifact> artifactMapByVersionlessId, ReleaseDescriptor releaseDescriptor) {
272 Artifact checkArtifact = getArtifactFromMap(artifact, artifactMapByVersionlessId);
273
274 return checkArtifact(checkArtifact, releaseDescriptor);
275 }
276
277 private static Artifact getArtifactFromMap(Artifact artifact, Map<String, Artifact> artifactMapByVersionlessId) {
278 String versionlessId = ArtifactUtils.versionlessKey(artifact);
279 Artifact checkArtifact = artifactMapByVersionlessId.get(versionlessId);
280
281 if (checkArtifact == null) {
282 checkArtifact = artifact;
283 }
284 return checkArtifact;
285 }
286
287 private static boolean checkArtifact(Artifact artifact, ReleaseDescriptor releaseDescriptor) {
288 String versionlessKey = ArtifactUtils.versionlessKey(artifact.getGroupId(), artifact.getArtifactId());
289 String releaseDescriptorResolvedVersion = releaseDescriptor.getDependencyReleaseVersion(versionlessKey);
290
291 boolean releaseDescriptorResolvedVersionIsSnapshot = releaseDescriptorResolvedVersion == null
292 || releaseDescriptorResolvedVersion.contains(Artifact.SNAPSHOT_VERSION);
293
294
295
296 boolean bannedVersion = artifact.isSnapshot()
297 && !artifact.getBaseVersion().equals(releaseDescriptor.getProjectOriginalVersion(versionlessKey))
298 && releaseDescriptorResolvedVersionIsSnapshot;
299
300
301
302 if (bannedVersion && releaseDescriptor.isAllowTimestampedSnapshots()) {
303 bannedVersion = artifact.getVersion().contains(Artifact.SNAPSHOT_VERSION);
304 }
305
306 return bannedVersion;
307 }
308
309 @Override
310 public ReleaseResult simulate(
311 ReleaseDescriptor releaseDescriptor,
312 ReleaseEnvironment releaseEnvironment,
313 List<MavenProject> reactorProjects)
314 throws ReleaseExecutionException, ReleaseFailureException {
315
316 return execute(releaseDescriptor, releaseEnvironment, reactorProjects);
317 }
318
319 private void printSnapshotDependencies(Set<Artifact> snapshotsSet, StringBuilder message) {
320 List<Artifact> snapshotsList = new ArrayList<>(snapshotsSet);
321
322 Collections.sort(snapshotsList);
323
324 for (Artifact artifact : snapshotsList) {
325 message.append(" ");
326
327 message.append(artifact);
328
329 message.append("\n");
330 }
331 }
332
333 private void resolveSnapshots(
334 Set<Artifact> projectDependencies,
335 Set<Artifact> reportDependencies,
336 Set<Artifact> extensionDependencies,
337 Set<Artifact> pluginDependencies,
338 ReleaseDescriptor releaseDescriptor)
339 throws ReleaseExecutionException {
340 try {
341 String autoResolveSnapshots = releaseDescriptor.getAutoResolveSnapshots();
342 if (resolveSnapshot == null) {
343 prompter.get().showMessage(RESOLVE_SNAPSHOT_MESSAGE);
344 if (autoResolveSnapshots != null) {
345 resolveSnapshot = "yes";
346 prompter.get().showMessage(RESOLVE_SNAPSHOT_PROMPT + " " + resolveSnapshot);
347 } else {
348 resolveSnapshot = prompter.get().prompt(RESOLVE_SNAPSHOT_PROMPT, Arrays.asList("yes", "no"), "no");
349 }
350 }
351
352 if (resolveSnapshot.toLowerCase(Locale.ENGLISH).startsWith("y")) {
353 if (resolveSnapshotType == null) {
354 prompter.get().showMessage(RESOLVE_SNAPSHOT_TYPE_MESSAGE);
355 int defaultAnswer = -1;
356 if (autoResolveSnapshots != null) {
357 if ("all".equalsIgnoreCase(autoResolveSnapshots)) {
358 defaultAnswer = 0;
359 } else if ("dependencies".equalsIgnoreCase(autoResolveSnapshots)) {
360 defaultAnswer = 1;
361 } else if ("plugins".equalsIgnoreCase(autoResolveSnapshots)) {
362 defaultAnswer = 2;
363 } else if ("reports".equalsIgnoreCase(autoResolveSnapshots)) {
364 defaultAnswer = 3;
365 } else if ("extensions".equalsIgnoreCase(autoResolveSnapshots)) {
366 defaultAnswer = 4;
367 } else {
368 try {
369 defaultAnswer = Integer.parseInt(autoResolveSnapshots);
370 } catch (NumberFormatException e) {
371 throw new ReleaseExecutionException(e.getMessage(), e);
372 }
373 }
374 }
375 if (defaultAnswer >= 0 && defaultAnswer <= 4) {
376 prompter.get().showMessage(RESOLVE_SNAPSHOT_TYPE_PROMPT + " " + autoResolveSnapshots);
377 resolveSnapshotType = Integer.toString(defaultAnswer);
378 } else {
379 resolveSnapshotType = prompter.get()
380 .prompt(RESOLVE_SNAPSHOT_TYPE_PROMPT, Arrays.asList("0", "1", "2", "3"), "1");
381 }
382 }
383
384 switch (Integer.parseInt(resolveSnapshotType.toLowerCase(Locale.ENGLISH))) {
385
386 case 0:
387 processSnapshot(projectDependencies, releaseDescriptor, autoResolveSnapshots);
388 processSnapshot(pluginDependencies, releaseDescriptor, autoResolveSnapshots);
389 processSnapshot(reportDependencies, releaseDescriptor, autoResolveSnapshots);
390 processSnapshot(extensionDependencies, releaseDescriptor, autoResolveSnapshots);
391 break;
392
393
394 case 1:
395 processSnapshot(projectDependencies, releaseDescriptor, autoResolveSnapshots);
396 break;
397
398
399 case 2:
400 processSnapshot(pluginDependencies, releaseDescriptor, autoResolveSnapshots);
401 break;
402
403
404 case 3:
405 processSnapshot(reportDependencies, releaseDescriptor, autoResolveSnapshots);
406 break;
407
408
409 case 4:
410 processSnapshot(extensionDependencies, releaseDescriptor, autoResolveSnapshots);
411 break;
412
413 default:
414 }
415 }
416 } catch (PrompterException | VersionParseException e) {
417 throw new ReleaseExecutionException(e.getMessage(), e);
418 }
419 }
420
421 private void processSnapshot(
422 Set<Artifact> snapshotSet, ReleaseDescriptor releaseDescriptor, String autoResolveSnapshots)
423 throws PrompterException, VersionParseException {
424 Iterator<Artifact> iterator = snapshotSet.iterator();
425
426 while (iterator.hasNext()) {
427 Artifact currentArtifact = iterator.next();
428 String versionlessKey = ArtifactUtils.versionlessKey(currentArtifact);
429
430 VersionInfo versionInfo = new DefaultVersionInfo(currentArtifact.getBaseVersion());
431 releaseDescriptor.addDependencyOriginalVersion(versionlessKey, versionInfo.toString());
432
433 prompter.get()
434 .showMessage("Dependency '" + versionlessKey + "' is a snapshot (" + currentArtifact.getVersion()
435 + ")\n");
436 String message = "Which release version should it be set to?";
437 String result;
438 if (null != autoResolveSnapshots) {
439 result = versionInfo.getReleaseVersionString();
440 prompter.get().showMessage(message + " " + result);
441 } else {
442 result = prompter.get().prompt(message, versionInfo.getReleaseVersionString());
443 }
444
445 releaseDescriptor.addDependencyReleaseVersion(versionlessKey, result);
446
447 iterator.remove();
448
449
450
451 VersionInfo nextVersionInfo = new DefaultVersionInfo(result);
452
453 String nextVersion;
454 if (nextVersionInfo.compareTo(versionInfo) > 0) {
455 nextVersion = nextVersionInfo.toString();
456 } else {
457 nextVersion = versionInfo.toString();
458 }
459
460 message = "What version should the dependency be reset to for development?";
461 if (null != autoResolveSnapshots) {
462 result = nextVersion;
463 prompter.get().showMessage(message + " " + result);
464 } else {
465 result = prompter.get().prompt(message, nextVersion);
466 }
467
468 releaseDescriptor.addDependencyDevelopmentVersion(versionlessKey, result);
469 }
470 }
471 }