View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.release.phase;
20  
21  import java.nio.file.FileSystems;
22  import java.nio.file.Paths;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.maven.artifact.ArtifactUtils;
29  import org.apache.maven.project.MavenProject;
30  import org.apache.maven.scm.manager.NoSuchScmProviderException;
31  import org.apache.maven.scm.provider.ScmProvider;
32  import org.apache.maven.scm.repository.ScmRepository;
33  import org.apache.maven.scm.repository.ScmRepositoryException;
34  import org.apache.maven.shared.release.ReleaseExecutionException;
35  import org.apache.maven.shared.release.ReleaseResult;
36  import org.apache.maven.shared.release.config.ReleaseDescriptor;
37  import org.apache.maven.shared.release.env.ReleaseEnvironment;
38  import org.apache.maven.shared.release.policy.PolicyException;
39  import org.apache.maven.shared.release.policy.version.VersionPolicy;
40  import org.apache.maven.shared.release.policy.version.VersionPolicyRequest;
41  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
42  import org.apache.maven.shared.release.util.CiFriendlyVersion;
43  import org.apache.maven.shared.release.util.ReleaseUtil;
44  import org.apache.maven.shared.release.versions.VersionParseException;
45  import org.codehaus.plexus.components.interactivity.Prompter;
46  import org.codehaus.plexus.components.interactivity.PrompterException;
47  import org.slf4j.Logger;
48  
49  import static java.util.Objects.requireNonNull;
50  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
51  
52  /**
53   * Map projects to their new versions after release / into the next development cycle.
54   * <p>
55   * The map-phases per goal are:
56   * <dl>
57   *  <dt>release:prepare</dt><dd>map-release-versions + map-development-versions; RD.isBranchCreation() = false</dd>
58   *  <dt>release:branch</dt><dd>map-branch-versions + map-development-versions; RD.isBranchCreation() = true</dd>
59   *  <dt>release:update-versions</dt><dd>map-development-versions; RD.isBranchCreation() = false</dd>
60   * </dl>
61   *
62   * <table>
63   *   <caption>MapVersionsPhase</caption>
64   *   <tr>
65   *     <th>MapVersionsPhase field</th><th>map-release-versions</th><th>map-branch-versions</th>
66   *     <th>map-development-versions</th>
67   *   </tr>
68   *   <tr>
69   *     <td>convertToSnapshot</td>     <td>false</td>               <td>true</td>               <td>true</td>
70   *   </tr>
71   *   <tr>
72   *     <td>convertToBranch</td>       <td>false</td>               <td>true</td>               <td>false</td>
73   *   </tr>
74   * </table>
75   *
76   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
77   * @author Robert Scholte
78   */
79  public abstract class AbstractMapVersionsPhase extends AbstractReleasePhase {
80      /**
81       * Tool that gets a configured SCM repository from release configuration.
82       */
83      private final ScmRepositoryConfigurator scmRepositoryConfigurator;
84  
85      /**
86       * Component used to prompt for input.
87       */
88      private final Prompter prompter;
89  
90      /**
91       * Component used for custom or default version policy
92       */
93      private final Map<String, VersionPolicy> versionPolicies;
94  
95      /**
96       * Whether to convert to a snapshot or a release.
97       */
98      private final boolean convertToSnapshot;
99  
100     /**
101      * Whether to convert to a snapshot or a release.
102      */
103     private final boolean convertToBranch;
104 
105     private final Set<String> exclusionPatterns = new HashSet<>();
106 
107     public AbstractMapVersionsPhase(
108             ScmRepositoryConfigurator scmRepositoryConfigurator,
109             Prompter prompter,
110             Map<String, VersionPolicy> versionPolicies,
111             boolean convertToSnapshot,
112             boolean convertToBranch) {
113         this.scmRepositoryConfigurator = requireNonNull(scmRepositoryConfigurator);
114         this.prompter = requireNonNull(prompter);
115         this.versionPolicies = requireNonNull(versionPolicies);
116         this.convertToSnapshot = convertToSnapshot;
117         this.convertToBranch = convertToBranch;
118     }
119 
120     @Override
121     public ReleaseResult execute(
122             ReleaseDescriptor releaseDescriptor,
123             ReleaseEnvironment releaseEnvironment,
124             List<MavenProject> reactorProjects)
125             throws ReleaseExecutionException {
126         ReleaseResult result = new ReleaseResult();
127 
128         List<String> additionalExcludes = releaseDescriptor.getCheckModificationExcludes();
129 
130         if (additionalExcludes != null) {
131             exclusionPatterns.addAll(additionalExcludes);
132         }
133 
134         MavenProject rootProject = ReleaseUtil.getRootProject(reactorProjects);
135 
136         if (releaseDescriptor.isAutoVersionSubmodules() && ArtifactUtils.isSnapshot(rootProject.getVersion())) {
137             // get the root project
138             MavenProject project = rootProject;
139 
140             String projectId = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
141 
142             String nextVersion = resolveNextVersion(project, projectId, releaseDescriptor, releaseEnvironment);
143 
144             if (!convertToSnapshot) {
145                 releaseDescriptor.addReleaseVersion(projectId, nextVersion);
146             } else if (releaseDescriptor.isBranchCreation() && convertToBranch) {
147                 releaseDescriptor.addReleaseVersion(projectId, nextVersion);
148             } else {
149                 releaseDescriptor.addDevelopmentVersion(projectId, nextVersion);
150             }
151 
152             for (MavenProject subProject : reactorProjects) {
153                 String subProjectId = ArtifactUtils.versionlessKey(subProject.getGroupId(), subProject.getArtifactId());
154 
155                 if (convertToSnapshot) {
156                     String subProjectNextVersion = releaseDescriptor.getProjectDevelopmentVersion(subProjectId);
157                     String v;
158                     if (subProjectNextVersion != null) {
159                         v = subProjectNextVersion;
160                     } else if (ArtifactUtils.isSnapshot(subProject.getVersion())) {
161                         v = nextVersion;
162                     } else {
163                         v = subProject.getVersion();
164                     }
165 
166                     if (releaseDescriptor.isBranchCreation() && convertToBranch) {
167                         releaseDescriptor.addReleaseVersion(subProjectId, v);
168                     } else {
169                         releaseDescriptor.addDevelopmentVersion(subProjectId, v);
170                     }
171                 } else {
172                     String subProjectNextVersion = releaseDescriptor.getProjectReleaseVersion(subProjectId);
173                     if (subProjectNextVersion != null) {
174                         releaseDescriptor.addReleaseVersion(subProjectId, subProjectNextVersion);
175                     } else {
176                         releaseDescriptor.addReleaseVersion(subProjectId, nextVersion);
177                     }
178                 }
179             }
180         } else {
181 
182             for (MavenProject project : reactorProjects) {
183                 String projectId = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
184 
185                 boolean isExcludedPathFound = false;
186                 if (project.getFile() != null) {
187                     isExcludedPathFound = exclusionPatterns.stream()
188                             .anyMatch(exclusionPattern -> FileSystems.getDefault()
189                                     .getPathMatcher("glob:" + exclusionPattern)
190                                     .matches(Paths.get(project.getFile().getPath())));
191                 }
192                 if (!isExcludedPathFound) {
193                     String nextVersion = resolveNextVersion(project, projectId, releaseDescriptor, releaseEnvironment);
194                     if (!convertToSnapshot) {
195                         releaseDescriptor.addReleaseVersion(projectId, nextVersion);
196                     } else if (releaseDescriptor.isBranchCreation() && convertToBranch) {
197                         releaseDescriptor.addReleaseVersion(projectId, nextVersion);
198                     } else {
199                         releaseDescriptor.addDevelopmentVersion(projectId, nextVersion);
200                     }
201                 }
202             }
203         }
204 
205         result.setResultCode(ReleaseResult.SUCCESS);
206 
207         return result;
208     }
209 
210     private String resolveNextVersion(
211             MavenProject project,
212             String projectId,
213             ReleaseDescriptor releaseDescriptor,
214             ReleaseEnvironment releaseEnvironment)
215             throws ReleaseExecutionException {
216         String defaultVersion;
217         if (convertToBranch) {
218             // no branch modification
219             if (!(releaseDescriptor.isUpdateBranchVersions()
220                     && (ArtifactUtils.isSnapshot(project.getVersion())
221                             || releaseDescriptor.isUpdateVersionsToSnapshot()))) {
222                 return project.getVersion();
223             }
224 
225             defaultVersion = getReleaseVersion(projectId, releaseDescriptor);
226         } else if (!convertToSnapshot) // map-release-version
227         {
228             defaultVersion = getReleaseVersion(projectId, releaseDescriptor);
229         } else if (releaseDescriptor.isBranchCreation()) {
230             // no working copy modification
231             if (!(ArtifactUtils.isSnapshot(project.getVersion()) && releaseDescriptor.isUpdateWorkingCopyVersions())) {
232                 return project.getVersion();
233             }
234 
235             defaultVersion = getDevelopmentVersion(projectId, releaseDescriptor);
236         } else {
237             // no working copy modification
238             if (!(releaseDescriptor.isUpdateWorkingCopyVersions())) {
239                 return project.getVersion();
240             }
241 
242             defaultVersion = getDevelopmentVersion(projectId, releaseDescriptor);
243         }
244         // @todo validate default version, maybe with DefaultArtifactVersion
245 
246         String suggestedVersion = null;
247         String nextVersion = defaultVersion;
248         String messageFormat = null;
249         try {
250             while (nextVersion == null || ArtifactUtils.isSnapshot(nextVersion) != convertToSnapshot) {
251                 if (suggestedVersion == null) {
252                     String baseVersion = null;
253                     if (convertToSnapshot) {
254                         baseVersion = getReleaseVersion(projectId, releaseDescriptor);
255                     }
256                     // unspecified and unmapped version, so use project version
257                     if (baseVersion == null) {
258                         baseVersion = project.getVersion();
259                     }
260 
261                     try {
262                         try {
263                             suggestedVersion = resolveSuggestedVersion(
264                                     project, baseVersion, releaseDescriptor, releaseEnvironment);
265                         } catch (VersionParseException e) {
266                             if (releaseDescriptor.isInteractive()) {
267                                 suggestedVersion =
268                                         resolveSuggestedVersion(project, "1.0", releaseDescriptor, releaseEnvironment);
269                             } else {
270                                 throw new ReleaseExecutionException(
271                                         "Error parsing version, cannot determine next " + "version: " + e.getMessage(),
272                                         e);
273                             }
274                         }
275                     } catch (PolicyException | VersionParseException e) {
276                         throw new ReleaseExecutionException(e.getMessage(), e);
277                     }
278                 }
279 
280                 if (releaseDescriptor.isInteractive()) {
281                     if (messageFormat == null) {
282                         messageFormat = "What is the " + getContextString(releaseDescriptor) + " version for \"%s\"? ("
283                                 + buffer().project("%s") + ")";
284                     }
285                     String message = String.format(messageFormat, project.getName(), project.getArtifactId());
286                     nextVersion = prompter.prompt(message, suggestedVersion);
287 
288                     // @todo validate next version, maybe with DefaultArtifactVersion
289                 } else if (defaultVersion == null) {
290                     nextVersion = suggestedVersion;
291                 } else if (convertToSnapshot) {
292                     throw new ReleaseExecutionException(defaultVersion + " is invalid, expected a snapshot");
293                 } else {
294                     throw new ReleaseExecutionException(defaultVersion + " is invalid, expected a non-snapshot");
295                 }
296             }
297         } catch (PrompterException e) {
298             throw new ReleaseExecutionException("Error reading version from input handler: " + e.getMessage(), e);
299         }
300         return nextVersion;
301     }
302 
303     private String getContextString(ReleaseDescriptor releaseDescriptor) {
304         if (convertToBranch) {
305             return "branch";
306         }
307         if (!convertToSnapshot) {
308             return "release";
309         }
310         if (releaseDescriptor.isBranchCreation()) {
311             return "new working copy";
312         }
313         return "new development";
314     }
315 
316     private String resolveSuggestedVersion(
317             MavenProject project,
318             String baseVersion,
319             ReleaseDescriptor releaseDescriptor,
320             ReleaseEnvironment releaseEnvironment)
321             throws PolicyException, VersionParseException {
322         String policyId = releaseDescriptor.getProjectVersionPolicyId();
323         VersionPolicy policy = versionPolicies.get(policyId);
324         if (policy == null) {
325             throw new PolicyException("Policy '" + policyId + "' is unknown, available: " + versionPolicies.keySet());
326         }
327 
328         VersionPolicyRequest request = new VersionPolicyRequest().setVersion(baseVersion);
329 
330         if (releaseDescriptor.getProjectVersionPolicyConfig() != null) {
331             request.setConfig(releaseDescriptor.getProjectVersionPolicyConfig().toString());
332         }
333         request.setWorkingDirectory(releaseDescriptor.getWorkingDirectory());
334 
335         if (CiFriendlyVersion.isCiFriendlyVersion(getOriginalVersion(project))) {
336             String sha1 = CiFriendlyVersion.resolveSha1Property(project.getProperties(), releaseDescriptor);
337             request.setVersion(baseVersion.replace(sha1, ""));
338         }
339 
340         if (scmRepositoryConfigurator != null && releaseDescriptor.getScmSourceUrl() != null) {
341             try {
342                 ScmRepository repository = scmRepositoryConfigurator.getConfiguredRepository(
343                         releaseDescriptor, releaseEnvironment.getSettings());
344 
345                 ScmProvider provider = scmRepositoryConfigurator.getRepositoryProvider(repository);
346 
347                 request.setScmRepository(repository);
348                 request.setScmProvider(provider);
349             } catch (ScmRepositoryException | NoSuchScmProviderException e) {
350                 Logger logger = getLogger();
351                 if (logger.isWarnEnabled()) {
352                     logger.warn("Next Version will NOT be based on the version control: {}", e.getMessage());
353                 } else {
354                     if (logger.isDebugEnabled()) {
355                         logger.warn("Next Version will NOT be based on the version control", e);
356                     }
357                 }
358             }
359         }
360         return convertToSnapshot
361                 ? policy.getDevelopmentVersion(request).getVersion()
362                 : policy.getReleaseVersion(request).getVersion();
363     }
364 
365     private static String getOriginalVersion(MavenProject project) {
366         String version = null;
367         while (version == null && project != null) {
368             if (project.getOriginalModel() != null) {
369                 version = project.getOriginalModel().getVersion();
370             } else {
371                 version = project.getVersion();
372             }
373             project = project.getParent();
374         }
375         return version;
376     }
377 
378     private String getDevelopmentVersion(String projectId, ReleaseDescriptor releaseDescriptor) {
379         String projectVersion = releaseDescriptor.getProjectDevelopmentVersion(projectId);
380 
381         if (projectVersion == null || projectVersion.isEmpty()) {
382             projectVersion = releaseDescriptor.getDefaultDevelopmentVersion();
383         }
384 
385         if (projectVersion == null || projectVersion.isEmpty()) {
386             return null;
387         }
388 
389         return projectVersion;
390     }
391 
392     private String getReleaseVersion(String projectId, ReleaseDescriptor releaseDescriptor) {
393         String projectVersion = releaseDescriptor.getProjectReleaseVersion(projectId);
394 
395         if (projectVersion == null || projectVersion.isEmpty()) {
396             projectVersion = releaseDescriptor.getDefaultReleaseVersion();
397         }
398 
399         if (projectVersion == null || projectVersion.isEmpty()) {
400             return null;
401         }
402 
403         return projectVersion;
404     }
405 
406     @Override
407     public ReleaseResult simulate(
408             ReleaseDescriptor releaseDescriptor,
409             ReleaseEnvironment releaseEnvironment,
410             List<MavenProject> reactorProjects)
411             throws ReleaseExecutionException {
412         ReleaseResult result = new ReleaseResult();
413 
414         // It makes no modifications, so simulate is the same as execute
415         execute(releaseDescriptor, releaseEnvironment, reactorProjects);
416 
417         result.setResultCode(ReleaseResult.SUCCESS);
418 
419         return result;
420     }
421 }