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