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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.ArtifactUtils;
24  import org.apache.maven.artifact.factory.ArtifactFactory;
25  import org.apache.maven.project.MavenProject;
26  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
27  import org.apache.maven.shared.release.ReleaseExecutionException;
28  import org.apache.maven.shared.release.ReleaseFailureException;
29  import org.apache.maven.shared.release.ReleaseResult;
30  import org.apache.maven.shared.release.config.ReleaseDescriptor;
31  import org.apache.maven.shared.release.env.ReleaseEnvironment;
32  import org.apache.maven.shared.release.versions.DefaultVersionInfo;
33  import org.apache.maven.shared.release.versions.VersionInfo;
34  import org.apache.maven.shared.release.versions.VersionParseException;
35  import org.codehaus.plexus.components.interactivity.Prompter;
36  import org.codehaus.plexus.components.interactivity.PrompterException;
37  
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.Collections;
41  import java.util.HashMap;
42  import java.util.HashSet;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Locale;
46  import java.util.Map;
47  import java.util.Set;
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      public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment, List<MavenProject> reactorProjects )
84          throws ReleaseExecutionException, ReleaseFailureException
85      {
86          ReleaseResult result = new ReleaseResult();
87  
88          if ( !releaseDescriptor.isAllowTimestampedSnapshots() )
89          {
90              logInfo( result, "Checking dependencies and plugins for snapshots ..." );
91  
92              Map<String, String> originalVersions = releaseDescriptor.getOriginalVersions( reactorProjects );
93  
94              for ( MavenProject project : reactorProjects )
95              {
96                  checkProject( project, originalVersions, releaseDescriptor );
97              }
98          }
99          else
100         {
101             logInfo( result, "Ignoring SNAPSHOT depenedencies and plugins ..." );
102         }
103         result.setResultCode( ReleaseResult.SUCCESS );
104 
105         return result;
106     }
107 
108     private void checkProject( MavenProject project, Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor )
109         throws ReleaseFailureException, ReleaseExecutionException
110     {
111         @SuppressWarnings( "unchecked" )
112         Map<String, Artifact> artifactMap = ArtifactUtils.artifactMapByVersionlessId( project.getArtifacts() );
113 
114         Set<Artifact> snapshotDependencies = new HashSet<Artifact>();
115         Set<Artifact> snapshotReportDependencies = new HashSet<Artifact>();
116         Set<Artifact> snapshotExtensionsDependencies = new HashSet<Artifact>();
117         Set<Artifact> snapshotPluginDependencies = new HashSet<Artifact>();
118 
119         if ( project.getParentArtifact() != null )
120         {
121             if ( checkArtifact( project.getParentArtifact(), originalVersions, artifactMap, releaseDescriptor ) )
122             {
123                 snapshotDependencies.add( project.getParentArtifact() );
124             }
125         }
126 
127         try
128         {
129             @SuppressWarnings( "unchecked" )
130             Set<Artifact> dependencyArtifacts = project.createArtifacts( artifactFactory, null, null );
131 
132             for ( Artifact artifact : dependencyArtifacts )
133             {
134                 if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
135                 {
136                     snapshotDependencies.add( getArtifactFromMap( artifact, artifactMap ) );
137                 }
138             }
139         }
140         catch ( InvalidDependencyVersionException e )
141         {
142             throw new ReleaseExecutionException( "Failed to create dependency artifacts", e );
143         }
144 
145         @SuppressWarnings( "unchecked" )
146         Set<Artifact> pluginArtifacts = project.getPluginArtifacts();
147 
148         for ( Artifact artifact : pluginArtifacts )
149         {
150             if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
151             {
152                 boolean addToFailures;
153 
154                 if ( "org.apache.maven.plugins".equals( artifact.getGroupId() ) && "maven-release-plugin".equals(
155                     artifact.getArtifactId() ) )
156                 {
157                     // It's a snapshot of the release plugin. Maybe just testing - ask
158                     // By default, we fail as for any other plugin
159                     if ( releaseDescriptor.isSnapshotReleasePluginAllowed() )
160                     {
161                         addToFailures = false;
162                     }
163                     else if ( releaseDescriptor.isInteractive() )
164                     {
165                         try
166                         {
167                             String result;
168                             if ( !releaseDescriptor.isSnapshotReleasePluginAllowed() )
169                             {
170                                 prompter.showMessage( "This project relies on a SNAPSHOT of the release plugin. "
171                                                           + "This may be necessary during testing.\n" );
172                                 result = prompter.prompt( "Do you want to continue with the release?",
173                                                           Arrays.asList( new String[]{ "yes", "no" } ), "no" );
174                             }
175                             else
176                             {
177                                 result = "yes";
178                             }
179 
180                             if ( result.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
181                             {
182                                 addToFailures = false;
183                                 releaseDescriptor.setSnapshotReleasePluginAllowed( true );
184                             }
185                             else
186                             {
187                                 addToFailures = true;
188                             }
189                         }
190                         catch ( PrompterException e )
191                         {
192                             throw new ReleaseExecutionException( e.getMessage(), e );
193                         }
194                     }
195                     else
196                     {
197                         addToFailures = true;
198                     }
199                 }
200                 else
201                 {
202                     addToFailures = true;
203                 }
204 
205                 if ( addToFailures )
206                 {
207                     snapshotPluginDependencies.add( artifact );
208                 }
209             }
210         }
211 
212         @SuppressWarnings( "unchecked" )
213         Set<Artifact> reportArtifacts = project.getReportArtifacts();
214 
215         for ( Artifact artifact : reportArtifacts )
216         {
217             if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
218             {
219                 //snapshotDependencies.add( artifact );
220                 snapshotReportDependencies.add( artifact );
221             }
222         }
223 
224         @SuppressWarnings( "unchecked" )
225         Set<Artifact> extensionArtifacts = project.getExtensionArtifacts();
226 
227         for ( Artifact artifact : extensionArtifacts )
228         {
229             if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
230             {
231                 snapshotExtensionsDependencies.add( artifact );
232             }
233         }
234 
235         if ( !snapshotDependencies.isEmpty() || !snapshotReportDependencies.isEmpty()
236                         || !snapshotExtensionsDependencies.isEmpty() || !snapshotPluginDependencies.isEmpty() )
237         {
238             if ( releaseDescriptor.isInteractive() )
239             {
240                 resolveSnapshots( snapshotDependencies, snapshotReportDependencies, snapshotExtensionsDependencies,
241                                   snapshotPluginDependencies, releaseDescriptor );
242             }
243 
244             if ( !snapshotDependencies.isEmpty() || !snapshotReportDependencies.isEmpty()
245                             || !snapshotExtensionsDependencies.isEmpty() || !snapshotPluginDependencies.isEmpty() )
246             {
247                 StringBuilder message = new StringBuilder();
248 
249                 printSnapshotDependencies( snapshotDependencies, message );
250                 printSnapshotDependencies( snapshotReportDependencies, message );
251                 printSnapshotDependencies( snapshotExtensionsDependencies, message );
252                 printSnapshotDependencies( snapshotPluginDependencies, message );
253                 message.append( "in project '" + project.getName() + "' (" + project.getId() + ")" );
254 
255                 throw new ReleaseFailureException(
256                     "Can't release project due to non released dependencies :\n" + message );
257             }
258         }
259     }
260 
261     private static boolean checkArtifact( Artifact artifact, Map<String, String> originalVersions, Map<String, Artifact> artifactMapByVersionlessId, ReleaseDescriptor releaseDescriptor )
262     {
263         Artifact checkArtifact = getArtifactFromMap( artifact, artifactMapByVersionlessId );
264 
265         return checkArtifact( checkArtifact, originalVersions, releaseDescriptor );
266     }
267 
268     private static Artifact getArtifactFromMap( Artifact artifact, Map<String, Artifact> artifactMapByVersionlessId )
269     {
270         String versionlessId = ArtifactUtils.versionlessKey( artifact );
271         Artifact checkArtifact = artifactMapByVersionlessId.get( versionlessId );
272 
273         if ( checkArtifact == null )
274         {
275             checkArtifact = artifact;
276         }
277         return checkArtifact;
278     }
279 
280     private static boolean checkArtifact( Artifact artifact, Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor )
281     {
282         String versionlessArtifactKey = ArtifactUtils.versionlessKey( artifact.getGroupId(), artifact.getArtifactId() );
283 
284         // We are only looking at dependencies external to the project - ignore anything found in the reactor as
285         // it's version will be updated
286         boolean result =
287             artifact.isSnapshot() && !artifact.getBaseVersion().equals( originalVersions.get( versionlessArtifactKey ) );
288 
289         // If we have a snapshot but allowTimestampedSnapshots is true, accept the artifact if the version
290         // indicates that it is a timestamped snapshot.
291         if ( result && releaseDescriptor.isAllowTimestampedSnapshots() )
292         {
293             result = artifact.getVersion().indexOf( Artifact.SNAPSHOT_VERSION ) >= 0;
294         }
295 
296         return result;
297     }
298 
299     public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment, List<MavenProject> reactorProjects )
300         throws ReleaseExecutionException, ReleaseFailureException
301     {
302         // It makes no modifications, so simulate is the same as execute
303         return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
304     }
305 
306     public void setPrompter( Prompter prompter )
307     {
308         this.prompter = prompter;
309     }
310 
311     private StringBuilder printSnapshotDependencies( Set<Artifact> snapshotsSet, StringBuilder message )
312     {
313         List<Artifact> snapshotsList = new ArrayList<Artifact>( snapshotsSet );
314 
315         Collections.sort( snapshotsList );
316 
317         for ( Artifact artifact : snapshotsList )
318         {
319             message.append( "    " );
320 
321             message.append( artifact );
322 
323             message.append( "\n" );
324         }
325 
326         return message;
327     }
328 
329     private void resolveSnapshots( Set<Artifact> projectDependencies, Set<Artifact> reportDependencies, Set<Artifact> extensionDependencies,
330                                    Set<Artifact> pluginDependencies, ReleaseDescriptor releaseDescriptor )
331         throws ReleaseExecutionException
332     {
333         try
334         {
335             prompter.showMessage( RESOLVE_SNAPSHOT_MESSAGE );
336             String result =
337                 prompter.prompt( RESOLVE_SNAPSHOT_PROMPT, Arrays.asList( new String[]{"yes", "no"} ), "no" );
338 
339             if ( result.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
340             {
341                 Map<String, Map<String, String>> resolvedSnapshots = null;
342                 prompter.showMessage( RESOLVE_SNAPSHOT_TYPE_MESSAGE );
343                 result = prompter.prompt( RESOLVE_SNAPSHOT_TYPE_PROMPT,
344                                           Arrays.asList( new String[]{"0", "1", "2", "3"} ), "1" );
345 
346                 switch ( Integer.parseInt( result.toLowerCase( Locale.ENGLISH ) ) )
347                 {
348                     // all
349                     case 0:
350                         resolvedSnapshots = processSnapshot( projectDependencies );
351                         resolvedSnapshots.putAll( processSnapshot( pluginDependencies ) );
352                         resolvedSnapshots.putAll( processSnapshot( reportDependencies ) );
353                         resolvedSnapshots.putAll( processSnapshot( extensionDependencies ) );
354                         break;
355 
356                         // project dependencies
357                     case 1:
358                         resolvedSnapshots = processSnapshot( projectDependencies );
359                         break;
360 
361                         // plugins
362                     case 2:
363                         resolvedSnapshots = processSnapshot( pluginDependencies );
364                         break;
365 
366                         // reports
367                     case 3:
368                         resolvedSnapshots = processSnapshot( reportDependencies );
369                         break;
370 
371                         // extensions
372                     case 4:
373                         resolvedSnapshots = processSnapshot( extensionDependencies );
374                         break;
375                 }
376 
377                 releaseDescriptor.getResolvedSnapshotDependencies().putAll( resolvedSnapshots );
378             }
379         }
380         catch ( PrompterException e )
381         {
382             throw new ReleaseExecutionException( e.getMessage(), e );
383         }
384         catch ( VersionParseException e )
385         {
386             throw new ReleaseExecutionException( e.getMessage(), e );
387         }
388     }
389 
390     private Map<String, Map<String, String>> processSnapshot( Set<Artifact> snapshotSet )
391         throws PrompterException, VersionParseException
392     {
393         Map<String, Map<String, String>> resolvedSnapshots = new HashMap<String, Map<String, String>>();
394         Iterator<Artifact> iterator = snapshotSet.iterator();
395 
396         while ( iterator.hasNext() )
397         {
398             Artifact currentArtifact = iterator.next();
399             String versionlessKey = ArtifactUtils.versionlessKey( currentArtifact );
400 
401             Map<String, String> versionMap = new HashMap<String, String>();
402             VersionInfo versionInfo = new DefaultVersionInfo( currentArtifact.getVersion() );
403             versionMap.put( ReleaseDescriptor.ORIGINAL_VERSION, versionInfo.toString() );
404 
405             prompter.showMessage(
406                 "Dependency '" + versionlessKey + "' is a snapshot (" + currentArtifact.getVersion() + ")\n" );
407             String result = prompter.prompt( "Which release version should it be set to?",
408                                              versionInfo.getReleaseVersionString() );
409             versionMap.put( ReleaseDescriptor.RELEASE_KEY, result );
410 
411             iterator.remove();
412 
413             // by default, keep the same version for the dependency after release, unless it was previously newer
414             // the user may opt to type in something different
415             VersionInfo nextVersionInfo = new DefaultVersionInfo( result );
416 
417             String nextVersion;
418             if ( nextVersionInfo.compareTo( versionInfo ) > 0 )
419             {
420                 nextVersion = nextVersionInfo.toString();
421             }
422             else
423             {
424                 nextVersion = versionInfo.toString();
425             }
426 
427             result = prompter.prompt( "What version should the dependency be reset to for development?", nextVersion );
428             versionMap.put( ReleaseDescriptor.DEVELOPMENT_KEY, result );
429 
430             resolvedSnapshots.put( versionlessKey, versionMap );
431         }
432 
433         return resolvedSnapshots;
434     }
435 }