View Javadoc
1   package org.apache.maven.shared.release.phase;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.artifact.ArtifactUtils;
35  import org.apache.maven.artifact.factory.ArtifactFactory;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
38  import org.apache.maven.shared.release.ReleaseExecutionException;
39  import org.apache.maven.shared.release.ReleaseFailureException;
40  import org.apache.maven.shared.release.ReleaseResult;
41  import org.apache.maven.shared.release.config.ReleaseDescriptor;
42  import org.apache.maven.shared.release.env.ReleaseEnvironment;
43  import org.apache.maven.shared.release.versions.DefaultVersionInfo;
44  import org.apache.maven.shared.release.versions.VersionInfo;
45  import org.apache.maven.shared.release.versions.VersionParseException;
46  import org.codehaus.plexus.components.interactivity.Prompter;
47  import org.codehaus.plexus.components.interactivity.PrompterException;
48  
49  /**
50   * Check the dependencies of all projects being released to see if there are any unreleased snapshots.
51   *
52   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
53   * @todo plugins with no version will be resolved to RELEASE which is not a snapshot, but remains unresolved to this point. This is a potential hole in the check, and should be revisited after the release pom writing is done and resolving versions to verify whether it is.
54   * @todo plugins injected by the lifecycle are not tested here. They will be injected with a RELEASE version so are covered under the above point.
55   * @plexus.component role="org.apache.maven.shared.release.phase.ReleasePhase" role-hint="check-dependency-snapshots"
56   */
57  public class CheckDependencySnapshotsPhase
58      extends AbstractReleasePhase
59  {
60      public static final String RESOLVE_SNAPSHOT_MESSAGE = "There are still some remaining snapshot dependencies.\n";
61  
62      public static final String RESOLVE_SNAPSHOT_PROMPT = "Do you want to resolve them now?";
63  
64      public static final String RESOLVE_SNAPSHOT_TYPE_MESSAGE = "Dependency type to resolve,";
65  
66      public static final String RESOLVE_SNAPSHOT_TYPE_PROMPT =
67          "specify the selection number ( 0:All 1:Project Dependencies 2:Plugins 3:Reports 4:Extensions ):";
68  
69      /**
70       * Component used to prompt for input.
71       *
72       * @plexus.requirement
73       */
74      private Prompter prompter;
75  
76      /**
77       * Component used to create artifacts
78       *
79       * @plexus.requirement
80       */
81      private ArtifactFactory artifactFactory;
82  
83      // Be aware of the difference between usedSnapshots and specifiedSnapshots:
84      // UsedSnapshots end up on the classpath.
85      // SpecifiedSnapshots are defined anywhere in the pom.
86      // We'll probably need to introduce specifiedSnapshots as well.
87      // @TODO MRELEASE-378: verify custom dependencies in plugins. Be aware of deprecated/removed Components in M3, such as PluginCollector
88      // @TODO MRELEASE-763: verify all dependencies in inactive profiles
89      private Set<Artifact> usedSnapshotDependencies = new HashSet<Artifact>();
90      private Set<Artifact> usedSnapshotReports = new HashSet<Artifact>();
91      private Set<Artifact> usedSnapshotExtensions = new HashSet<Artifact>();
92      private Set<Artifact> usedSnapshotPlugins = new HashSet<Artifact>();
93  
94      public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment, List<MavenProject> reactorProjects )
95          throws ReleaseExecutionException, ReleaseFailureException
96      {
97          ReleaseResult result = new ReleaseResult();
98  
99          if ( !releaseDescriptor.isAllowTimestampedSnapshots() )
100         {
101             logInfo( result, "Checking dependencies and plugins for snapshots ..." );
102 
103             Map<String, String> originalVersions = releaseDescriptor.getOriginalVersions( reactorProjects );
104 
105             for ( MavenProject project : reactorProjects )
106             {
107                 checkProject( project, originalVersions, releaseDescriptor );
108             }
109         }
110         else
111         {
112             logInfo( result, "Ignoring SNAPSHOT depenedencies and plugins ..." );
113         }
114         result.setResultCode( ReleaseResult.SUCCESS );
115 
116         return result;
117     }
118 
119     private void checkProject( MavenProject project, Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor )
120         throws ReleaseFailureException, ReleaseExecutionException
121     {
122         @SuppressWarnings( "unchecked" )
123         Map<String, Artifact> artifactMap = ArtifactUtils.artifactMapByVersionlessId( project.getArtifacts() );
124 
125         if ( project.getParentArtifact() != null )
126         {
127             if ( checkArtifact( project.getParentArtifact(), originalVersions, artifactMap, releaseDescriptor ) )
128             {
129                 usedSnapshotDependencies.add( project.getParentArtifact() );
130             }
131         }
132         
133         try
134         {
135             @SuppressWarnings( "unchecked" )
136             Set<Artifact> dependencyArtifacts = project.createArtifacts( artifactFactory, null, null );
137             checkDependencies( originalVersions, releaseDescriptor, artifactMap, dependencyArtifacts );
138         }
139         catch ( InvalidDependencyVersionException e )
140         {
141             throw new ReleaseExecutionException( "Failed to create dependency artifacts", e );
142         }
143         //@todo check dependencyManagement
144 
145         @SuppressWarnings( "unchecked" )
146         Set<Artifact> pluginArtifacts = project.getPluginArtifacts();
147         checkPlugins( originalVersions, releaseDescriptor, artifactMap, pluginArtifacts );
148         //@todo check pluginManagement
149 
150         @SuppressWarnings( "unchecked" )
151         Set<Artifact> reportArtifacts = project.getReportArtifacts();
152         checkReports( originalVersions, releaseDescriptor, artifactMap, reportArtifacts );
153 
154         @SuppressWarnings( "unchecked" )
155         Set<Artifact> extensionArtifacts = project.getExtensionArtifacts();
156         checkExtensions( originalVersions, releaseDescriptor, artifactMap, extensionArtifacts );
157         
158         //@todo check profiles
159 
160         if ( !usedSnapshotDependencies.isEmpty() || !usedSnapshotReports.isEmpty()
161                         || !usedSnapshotExtensions.isEmpty() || !usedSnapshotPlugins.isEmpty() )
162         {
163             if ( releaseDescriptor.isInteractive() )
164             {
165                 resolveSnapshots( usedSnapshotDependencies, usedSnapshotReports, usedSnapshotExtensions,
166                                   usedSnapshotPlugins, releaseDescriptor );
167             }
168 
169             if ( !usedSnapshotDependencies.isEmpty() || !usedSnapshotReports.isEmpty()
170                             || !usedSnapshotExtensions.isEmpty() || !usedSnapshotPlugins.isEmpty() )
171             {
172                 StringBuilder message = new StringBuilder();
173 
174                 printSnapshotDependencies( usedSnapshotDependencies, message );
175                 printSnapshotDependencies( usedSnapshotReports, message );
176                 printSnapshotDependencies( usedSnapshotExtensions, message );
177                 printSnapshotDependencies( usedSnapshotPlugins, message );
178                 message.append( "in project '" + project.getName() + "' (" + project.getId() + ")" );
179 
180                 throw new ReleaseFailureException(
181                     "Can't release project due to non released dependencies :\n" + message );
182             }
183         }
184     }
185 
186     private void checkPlugins( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
187                                Map<String, Artifact> artifactMap, Set<Artifact> pluginArtifacts )
188         throws ReleaseExecutionException
189     {
190         for ( Artifact artifact : pluginArtifacts )
191         {
192             if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
193             {
194                 boolean addToFailures;
195 
196                 if ( "org.apache.maven.plugins".equals( artifact.getGroupId() ) && "maven-release-plugin".equals(
197                     artifact.getArtifactId() ) )
198                 {
199                     // It's a snapshot of the release plugin. Maybe just testing - ask
200                     // By default, we fail as for any other plugin
201                     if ( releaseDescriptor.isSnapshotReleasePluginAllowed() )
202                     {
203                         addToFailures = false;
204                     }
205                     else if ( releaseDescriptor.isInteractive() )
206                     {
207                         try
208                         {
209                             String result;
210                             if ( !releaseDescriptor.isSnapshotReleasePluginAllowed() )
211                             {
212                                 prompter.showMessage( "This project relies on a SNAPSHOT of the release plugin. "
213                                                           + "This may be necessary during testing.\n" );
214                                 result = prompter.prompt( "Do you want to continue with the release?",
215                                                           Arrays.asList( "yes", "no" ), "no" );
216                             }
217                             else
218                             {
219                                 result = "yes";
220                             }
221 
222                             if ( result.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
223                             {
224                                 addToFailures = false;
225                                 releaseDescriptor.setSnapshotReleasePluginAllowed( true );
226                             }
227                             else
228                             {
229                                 addToFailures = true;
230                             }
231                         }
232                         catch ( PrompterException e )
233                         {
234                             throw new ReleaseExecutionException( e.getMessage(), e );
235                         }
236                     }
237                     else
238                     {
239                         addToFailures = true;
240                     }
241                 }
242                 else
243                 {
244                     addToFailures = true;
245                 }
246 
247                 if ( addToFailures )
248                 {
249                     usedSnapshotPlugins.add( artifact );
250                 }
251             }
252         }
253     }
254 
255     private void checkDependencies( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
256                                     Map<String, Artifact> artifactMap, Set<Artifact> dependencyArtifacts )
257     {
258         for ( Artifact artifact : dependencyArtifacts )
259         {
260             if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
261             {
262                 usedSnapshotDependencies.add( getArtifactFromMap( artifact, artifactMap ) );
263             }
264         }
265     }
266 
267     private void checkReports( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
268                                Map<String, Artifact> artifactMap, Set<Artifact> reportArtifacts )
269     {
270         for ( Artifact artifact : reportArtifacts )
271         {
272             if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
273             {
274                 //snapshotDependencies.add( artifact );
275                 usedSnapshotReports.add( artifact );
276             }
277         }
278     }
279 
280     private void checkExtensions( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
281                                   Map<String, Artifact> artifactMap, Set<Artifact> extensionArtifacts )
282     {
283         for ( Artifact artifact : extensionArtifacts )
284         {
285             if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
286             {
287                 usedSnapshotExtensions.add( artifact );
288             }
289         }
290     }
291 
292     private static boolean checkArtifact( Artifact artifact, Map<String, String> originalVersions, Map<String, Artifact> artifactMapByVersionlessId, ReleaseDescriptor releaseDescriptor )
293     {
294         Artifact checkArtifact = getArtifactFromMap( artifact, artifactMapByVersionlessId );
295 
296         return checkArtifact( checkArtifact, originalVersions, releaseDescriptor );
297     }
298 
299     private static Artifact getArtifactFromMap( Artifact artifact, Map<String, Artifact> artifactMapByVersionlessId )
300     {
301         String versionlessId = ArtifactUtils.versionlessKey( artifact );
302         Artifact checkArtifact = artifactMapByVersionlessId.get( versionlessId );
303 
304         if ( checkArtifact == null )
305         {
306             checkArtifact = artifact;
307         }
308         return checkArtifact;
309     }
310 
311     private static boolean checkArtifact( Artifact artifact, Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor )
312     {
313         String versionlessArtifactKey = ArtifactUtils.versionlessKey( artifact.getGroupId(), artifact.getArtifactId() );
314 
315         // We are only looking at dependencies external to the project - ignore anything found in the reactor as
316         // it's version will be updated
317         boolean result =
318             artifact.isSnapshot() && !artifact.getBaseVersion().equals( originalVersions.get( versionlessArtifactKey ) );
319 
320         // If we have a snapshot but allowTimestampedSnapshots is true, accept the artifact if the version
321         // indicates that it is a timestamped snapshot.
322         if ( result && releaseDescriptor.isAllowTimestampedSnapshots() )
323         {
324             result = artifact.getVersion().indexOf( Artifact.SNAPSHOT_VERSION ) >= 0;
325         }
326 
327         return result;
328     }
329 
330     public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment, List<MavenProject> reactorProjects )
331         throws ReleaseExecutionException, ReleaseFailureException
332     {
333         // It makes no modifications, so simulate is the same as execute
334         return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
335     }
336 
337     public void setPrompter( Prompter prompter )
338     {
339         this.prompter = prompter;
340     }
341 
342     private StringBuilder printSnapshotDependencies( Set<Artifact> snapshotsSet, StringBuilder message )
343     {
344         List<Artifact> snapshotsList = new ArrayList<Artifact>( snapshotsSet );
345 
346         Collections.sort( snapshotsList );
347 
348         for ( Artifact artifact : snapshotsList )
349         {
350             message.append( "    " );
351 
352             message.append( artifact );
353 
354             message.append( "\n" );
355         }
356 
357         return message;
358     }
359 
360     private void resolveSnapshots( Set<Artifact> projectDependencies, Set<Artifact> reportDependencies, Set<Artifact> extensionDependencies,
361                                    Set<Artifact> pluginDependencies, ReleaseDescriptor releaseDescriptor )
362         throws ReleaseExecutionException
363     {
364         try
365         {
366             prompter.showMessage( RESOLVE_SNAPSHOT_MESSAGE );
367             String result =
368                 prompter.prompt( RESOLVE_SNAPSHOT_PROMPT, Arrays.asList( "yes", "no" ), "no" );
369 
370             if ( result.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
371             {
372                 Map<String, Map<String, String>> resolvedSnapshots = null;
373                 prompter.showMessage( RESOLVE_SNAPSHOT_TYPE_MESSAGE );
374                 result = prompter.prompt( RESOLVE_SNAPSHOT_TYPE_PROMPT,
375                                           Arrays.asList( "0", "1", "2", "3" ), "1" );
376 
377                 switch ( Integer.parseInt( result.toLowerCase( Locale.ENGLISH ) ) )
378                 {
379                     // all
380                     case 0:
381                         resolvedSnapshots = processSnapshot( projectDependencies );
382                         resolvedSnapshots.putAll( processSnapshot( pluginDependencies ) );
383                         resolvedSnapshots.putAll( processSnapshot( reportDependencies ) );
384                         resolvedSnapshots.putAll( processSnapshot( extensionDependencies ) );
385                         break;
386 
387                         // project dependencies
388                     case 1:
389                         resolvedSnapshots = processSnapshot( projectDependencies );
390                         break;
391 
392                         // plugins
393                     case 2:
394                         resolvedSnapshots = processSnapshot( pluginDependencies );
395                         break;
396 
397                         // reports
398                     case 3:
399                         resolvedSnapshots = processSnapshot( reportDependencies );
400                         break;
401 
402                         // extensions
403                     case 4:
404                         resolvedSnapshots = processSnapshot( extensionDependencies );
405                         break;
406                 }
407 
408                 releaseDescriptor.getResolvedSnapshotDependencies().putAll( resolvedSnapshots );
409             }
410         }
411         catch ( PrompterException e )
412         {
413             throw new ReleaseExecutionException( e.getMessage(), e );
414         }
415         catch ( VersionParseException e )
416         {
417             throw new ReleaseExecutionException( e.getMessage(), e );
418         }
419     }
420 
421     private Map<String, Map<String, String>> processSnapshot( Set<Artifact> snapshotSet )
422         throws PrompterException, VersionParseException
423     {
424         Map<String, Map<String, String>> resolvedSnapshots = new HashMap<String, Map<String, String>>();
425         Iterator<Artifact> iterator = snapshotSet.iterator();
426 
427         while ( iterator.hasNext() )
428         {
429             Artifact currentArtifact = iterator.next();
430             String versionlessKey = ArtifactUtils.versionlessKey( currentArtifact );
431 
432             Map<String, String> versionMap = new HashMap<String, String>();
433             VersionInfo versionInfo = new DefaultVersionInfo( currentArtifact.getVersion() );
434             versionMap.put( ReleaseDescriptor.ORIGINAL_VERSION, versionInfo.toString() );
435 
436             prompter.showMessage(
437                 "Dependency '" + versionlessKey + "' is a snapshot (" + currentArtifact.getVersion() + ")\n" );
438             String result = prompter.prompt( "Which release version should it be set to?",
439                                              versionInfo.getReleaseVersionString() );
440             versionMap.put( ReleaseDescriptor.RELEASE_KEY, result );
441 
442             iterator.remove();
443 
444             // by default, keep the same version for the dependency after release, unless it was previously newer
445             // the user may opt to type in something different
446             VersionInfo nextVersionInfo = new DefaultVersionInfo( result );
447 
448             String nextVersion;
449             if ( nextVersionInfo.compareTo( versionInfo ) > 0 )
450             {
451                 nextVersion = nextVersionInfo.toString();
452             }
453             else
454             {
455                 nextVersion = versionInfo.toString();
456             }
457 
458             result = prompter.prompt( "What version should the dependency be reset to for development?", nextVersion );
459             versionMap.put( ReleaseDescriptor.DEVELOPMENT_KEY, result );
460 
461             resolvedSnapshots.put( versionlessKey, versionMap );
462         }
463 
464         return resolvedSnapshots;
465     }
466 }