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.util.List;
22  import java.util.Map;
23  
24  import org.apache.maven.artifact.ArtifactUtils;
25  import org.apache.maven.project.MavenProject;
26  import org.apache.maven.scm.manager.NoSuchScmProviderException;
27  import org.apache.maven.scm.provider.ScmProvider;
28  import org.apache.maven.scm.repository.ScmRepository;
29  import org.apache.maven.scm.repository.ScmRepositoryException;
30  import org.apache.maven.shared.release.ReleaseExecutionException;
31  import org.apache.maven.shared.release.ReleaseResult;
32  import org.apache.maven.shared.release.config.ReleaseDescriptor;
33  import org.apache.maven.shared.release.env.ReleaseEnvironment;
34  import org.apache.maven.shared.release.policy.PolicyException;
35  import org.apache.maven.shared.release.policy.version.VersionPolicy;
36  import org.apache.maven.shared.release.policy.version.VersionPolicyRequest;
37  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
38  import org.apache.maven.shared.release.util.ReleaseUtil;
39  import org.apache.maven.shared.release.versions.VersionParseException;
40  import org.codehaus.plexus.components.interactivity.Prompter;
41  import org.codehaus.plexus.components.interactivity.PrompterException;
42  import org.codehaus.plexus.util.StringUtils;
43  import org.slf4j.Logger;
44  
45  import static java.util.Objects.requireNonNull;
46  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
47  
48  /**
49   * Map projects to their new versions after release / into the next development cycle.
50   * <p>
51   * The map-phases per goal are:
52   * <dl>
53   *  <dt>release:prepare</dt><dd>map-release-versions + map-development-versions; RD.isBranchCreation() = false</dd>
54   *  <dt>release:branch</dt><dd>map-branch-versions + map-development-versions; RD.isBranchCreation() = true</dd>
55   *  <dt>release:update-versions</dt><dd>map-development-versions; RD.isBranchCreation() = false</dd>
56   * </dl>
57   *
58   * <table>
59   *   <caption>MapVersionsPhase</caption>
60   *   <tr>
61   *     <th>MapVersionsPhase field</th><th>map-release-versions</th><th>map-branch-versions</th>
62   *     <th>map-development-versions</th>
63   *   </tr>
64   *   <tr>
65   *     <td>convertToSnapshot</td>     <td>false</td>               <td>true</td>               <td>true</td>
66   *   </tr>
67   *   <tr>
68   *     <td>convertToBranch</td>       <td>false</td>               <td>true</td>               <td>false</td>
69   *   </tr>
70   * </table>
71   *
72   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
73   * @author Robert Scholte
74   */
75  public abstract class AbstractMapVersionsPhase extends AbstractReleasePhase {
76      /**
77       * Tool that gets a configured SCM repository from release configuration.
78       */
79      private final ScmRepositoryConfigurator scmRepositoryConfigurator;
80  
81      /**
82       * Component used to prompt for input.
83       */
84      private final Prompter prompter;
85  
86      /**
87       * Component used for custom or default version policy
88       */
89      private final Map<String, VersionPolicy> versionPolicies;
90  
91      /**
92       * Whether to convert to a snapshot or a release.
93       */
94      private final boolean convertToSnapshot;
95  
96      /**
97       * Whether to convert to a snapshot or a release.
98       */
99      private final boolean convertToBranch;
100 
101     public AbstractMapVersionsPhase(
102             ScmRepositoryConfigurator scmRepositoryConfigurator,
103             Prompter prompter,
104             Map<String, VersionPolicy> versionPolicies,
105             boolean convertToSnapshot,
106             boolean convertToBranch) {
107         this.scmRepositoryConfigurator = requireNonNull(scmRepositoryConfigurator);
108         this.prompter = requireNonNull(prompter);
109         this.versionPolicies = requireNonNull(versionPolicies);
110         this.convertToSnapshot = convertToSnapshot;
111         this.convertToBranch = convertToBranch;
112     }
113 
114     @Override
115     public ReleaseResult execute(
116             ReleaseDescriptor releaseDescriptor,
117             ReleaseEnvironment releaseEnvironment,
118             List<MavenProject> reactorProjects)
119             throws ReleaseExecutionException {
120         ReleaseResult result = new ReleaseResult();
121 
122         MavenProject rootProject = ReleaseUtil.getRootProject(reactorProjects);
123 
124         if (releaseDescriptor.isAutoVersionSubmodules() && ArtifactUtils.isSnapshot(rootProject.getVersion())) {
125             // get the root project
126             MavenProject project = rootProject;
127 
128             String projectId = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
129 
130             String nextVersion = resolveNextVersion(project, projectId, releaseDescriptor, releaseEnvironment);
131 
132             if (!convertToSnapshot) {
133                 releaseDescriptor.addReleaseVersion(projectId, nextVersion);
134             } else if (releaseDescriptor.isBranchCreation() && convertToBranch) {
135                 releaseDescriptor.addReleaseVersion(projectId, nextVersion);
136             } else {
137                 releaseDescriptor.addDevelopmentVersion(projectId, nextVersion);
138             }
139 
140             for (MavenProject subProject : reactorProjects) {
141                 String subProjectId = ArtifactUtils.versionlessKey(subProject.getGroupId(), subProject.getArtifactId());
142 
143                 if (convertToSnapshot) {
144                     String subProjectNextVersion = releaseDescriptor.getProjectDevelopmentVersion(subProjectId);
145                     String v;
146                     if (subProjectNextVersion != null) {
147                         v = subProjectNextVersion;
148                     } else if (ArtifactUtils.isSnapshot(subProject.getVersion())) {
149                         v = nextVersion;
150                     } else {
151                         v = subProject.getVersion();
152                     }
153 
154                     if (releaseDescriptor.isBranchCreation() && convertToBranch) {
155                         releaseDescriptor.addReleaseVersion(subProjectId, v);
156                     } else {
157                         releaseDescriptor.addDevelopmentVersion(subProjectId, v);
158                     }
159                 } else {
160                     String subProjectNextVersion = releaseDescriptor.getProjectReleaseVersion(subProjectId);
161                     if (subProjectNextVersion != null) {
162                         releaseDescriptor.addReleaseVersion(subProjectId, subProjectNextVersion);
163                     } else {
164                         releaseDescriptor.addReleaseVersion(subProjectId, nextVersion);
165                     }
166                 }
167             }
168         } else {
169             for (MavenProject project : reactorProjects) {
170                 String projectId = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
171 
172                 String nextVersion = resolveNextVersion(project, projectId, releaseDescriptor, releaseEnvironment);
173 
174                 if (!convertToSnapshot) {
175                     releaseDescriptor.addReleaseVersion(projectId, nextVersion);
176                 } else if (releaseDescriptor.isBranchCreation() && convertToBranch) {
177                     releaseDescriptor.addReleaseVersion(projectId, nextVersion);
178                 } else {
179                     releaseDescriptor.addDevelopmentVersion(projectId, nextVersion);
180                 }
181             }
182         }
183 
184         result.setResultCode(ReleaseResult.SUCCESS);
185 
186         return result;
187     }
188 
189     private String resolveNextVersion(
190             MavenProject project,
191             String projectId,
192             ReleaseDescriptor releaseDescriptor,
193             ReleaseEnvironment releaseEnvironment)
194             throws ReleaseExecutionException {
195         String defaultVersion;
196         if (convertToBranch) {
197             // no branch modification
198             if (!(releaseDescriptor.isUpdateBranchVersions()
199                     && (ArtifactUtils.isSnapshot(project.getVersion())
200                             || releaseDescriptor.isUpdateVersionsToSnapshot()))) {
201                 return project.getVersion();
202             }
203 
204             defaultVersion = getReleaseVersion(projectId, releaseDescriptor);
205         } else if (!convertToSnapshot) // map-release-version
206         {
207             defaultVersion = getReleaseVersion(projectId, releaseDescriptor);
208         } else if (releaseDescriptor.isBranchCreation()) {
209             // no working copy modification
210             if (!(ArtifactUtils.isSnapshot(project.getVersion()) && releaseDescriptor.isUpdateWorkingCopyVersions())) {
211                 return project.getVersion();
212             }
213 
214             defaultVersion = getDevelopmentVersion(projectId, releaseDescriptor);
215         } else {
216             // no working copy modification
217             if (!(releaseDescriptor.isUpdateWorkingCopyVersions())) {
218                 return project.getVersion();
219             }
220 
221             defaultVersion = getDevelopmentVersion(projectId, releaseDescriptor);
222         }
223         // @todo validate default version, maybe with DefaultArtifactVersion
224 
225         String suggestedVersion = null;
226         String nextVersion = defaultVersion;
227         String messageFormat = null;
228         try {
229             while (nextVersion == null || ArtifactUtils.isSnapshot(nextVersion) != convertToSnapshot) {
230                 if (suggestedVersion == null) {
231                     String baseVersion = null;
232                     if (convertToSnapshot) {
233                         baseVersion = getReleaseVersion(projectId, releaseDescriptor);
234                     }
235                     // unspecified and unmapped version, so use project version
236                     if (baseVersion == null) {
237                         baseVersion = project.getVersion();
238                     }
239 
240                     try {
241                         try {
242                             suggestedVersion =
243                                     resolveSuggestedVersion(baseVersion, releaseDescriptor, releaseEnvironment);
244                         } catch (VersionParseException e) {
245                             if (releaseDescriptor.isInteractive()) {
246                                 suggestedVersion =
247                                         resolveSuggestedVersion("1.0", releaseDescriptor, releaseEnvironment);
248                             } else {
249                                 throw new ReleaseExecutionException(
250                                         "Error parsing version, cannot determine next " + "version: " + e.getMessage(),
251                                         e);
252                             }
253                         }
254                     } catch (PolicyException | VersionParseException e) {
255                         throw new ReleaseExecutionException(e.getMessage(), e);
256                     }
257                 }
258 
259                 if (releaseDescriptor.isInteractive()) {
260                     if (messageFormat == null) {
261                         messageFormat = "What is the " + getContextString(releaseDescriptor) + " version for \"%s\"? ("
262                                 + buffer().project("%s") + ")";
263                     }
264                     String message = String.format(messageFormat, project.getName(), project.getArtifactId());
265                     nextVersion = prompter.prompt(message, suggestedVersion);
266 
267                     // @todo validate next version, maybe with DefaultArtifactVersion
268                 } else if (defaultVersion == null) {
269                     nextVersion = suggestedVersion;
270                 } else if (convertToSnapshot) {
271                     throw new ReleaseExecutionException(defaultVersion + " is invalid, expected a snapshot");
272                 } else {
273                     throw new ReleaseExecutionException(defaultVersion + " is invalid, expected a non-snapshot");
274                 }
275             }
276         } catch (PrompterException e) {
277             throw new ReleaseExecutionException("Error reading version from input handler: " + e.getMessage(), e);
278         }
279         return nextVersion;
280     }
281 
282     private String getContextString(ReleaseDescriptor releaseDescriptor) {
283         if (convertToBranch) {
284             return "branch";
285         }
286         if (!convertToSnapshot) {
287             return "release";
288         }
289         if (releaseDescriptor.isBranchCreation()) {
290             return "new working copy";
291         }
292         return "new development";
293     }
294 
295     private String resolveSuggestedVersion(
296             String baseVersion, ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment)
297             throws PolicyException, VersionParseException {
298         String policyId = releaseDescriptor.getProjectVersionPolicyId();
299         VersionPolicy policy = versionPolicies.get(policyId);
300         if (policy == null) {
301             throw new PolicyException("Policy '" + policyId + "' is unknown, available: " + versionPolicies.keySet());
302         }
303 
304         VersionPolicyRequest request = new VersionPolicyRequest().setVersion(baseVersion);
305 
306         if (releaseDescriptor.getProjectVersionPolicyConfig() != null) {
307             request.setConfig(releaseDescriptor.getProjectVersionPolicyConfig().toString());
308         }
309         request.setWorkingDirectory(releaseDescriptor.getWorkingDirectory());
310 
311         if (scmRepositoryConfigurator != null && releaseDescriptor.getScmSourceUrl() != null) {
312             try {
313                 ScmRepository repository = scmRepositoryConfigurator.getConfiguredRepository(
314                         releaseDescriptor, releaseEnvironment.getSettings());
315 
316                 ScmProvider provider = scmRepositoryConfigurator.getRepositoryProvider(repository);
317 
318                 request.setScmRepository(repository);
319                 request.setScmProvider(provider);
320             } catch (ScmRepositoryException | NoSuchScmProviderException e) {
321                 Logger logger = getLogger();
322                 if (logger.isWarnEnabled()) {
323                     logger.warn("Next Version will NOT be based on the version control: {}", e.getMessage());
324                 } else {
325                     if (logger.isDebugEnabled()) {
326                         logger.warn("Next Version will NOT be based on the version control", e);
327                     }
328                 }
329             }
330         }
331         return convertToSnapshot
332                 ? policy.getDevelopmentVersion(request).getVersion()
333                 : policy.getReleaseVersion(request).getVersion();
334     }
335 
336     private String getDevelopmentVersion(String projectId, ReleaseDescriptor releaseDescriptor) {
337         String projectVersion = releaseDescriptor.getProjectDevelopmentVersion(projectId);
338 
339         if (StringUtils.isEmpty(projectVersion)) {
340             projectVersion = releaseDescriptor.getDefaultDevelopmentVersion();
341         }
342 
343         if (StringUtils.isEmpty(projectVersion)) {
344             return null;
345         }
346 
347         return projectVersion;
348     }
349 
350     private String getReleaseVersion(String projectId, ReleaseDescriptor releaseDescriptor) {
351         String projectVersion = releaseDescriptor.getProjectReleaseVersion(projectId);
352 
353         if (StringUtils.isEmpty(projectVersion)) {
354             projectVersion = releaseDescriptor.getDefaultReleaseVersion();
355         }
356 
357         if (StringUtils.isEmpty(projectVersion)) {
358             return null;
359         }
360 
361         return projectVersion;
362     }
363 
364     @Override
365     public ReleaseResult simulate(
366             ReleaseDescriptor releaseDescriptor,
367             ReleaseEnvironment releaseEnvironment,
368             List<MavenProject> reactorProjects)
369             throws ReleaseExecutionException {
370         ReleaseResult result = new ReleaseResult();
371 
372         // It makes no modifications, so simulate is the same as execute
373         execute(releaseDescriptor, releaseEnvironment, reactorProjects);
374 
375         result.setResultCode(ReleaseResult.SUCCESS);
376 
377         return result;
378     }
379 }