Skip to content

Commit 9e0713b

Browse files
authored
[MRELEASE-1109] Support CI friendly versions (#198)
1 parent a0e4be4 commit 9e0713b

File tree

10 files changed

+282
-55
lines changed

10 files changed

+282
-55
lines changed

maven-release-manager/src/main/java/org/apache/maven/shared/release/phase/AbstractRewritePomsPhase.java

Lines changed: 102 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323
import java.text.DateFormat;
2424
import java.text.SimpleDateFormat;
2525
import java.util.ArrayList;
26+
import java.util.Arrays;
2627
import java.util.Collection;
2728
import java.util.Date;
2829
import java.util.List;
2930
import java.util.Map;
3031
import java.util.Properties;
3132
import java.util.TimeZone;
33+
import java.util.regex.Matcher;
34+
import java.util.regex.Pattern;
3235

3336
import org.apache.maven.artifact.Artifact;
3437
import org.apache.maven.artifact.ArtifactUtils;
@@ -89,6 +92,18 @@ public abstract class AbstractRewritePomsPhase extends AbstractReleasePhase impl
8992
*/
9093
private String modelETL = JDomModelETLFactory.NAME;
9194

95+
/**
96+
* Regular expression pattern matching Maven expressions (i.e. references to Maven properties).
97+
* The first group selects the property name the expression refers to.
98+
*/
99+
private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\{(.+)\\}");
100+
101+
/**
102+
* All Maven properties allowed to be referenced in parent versions via expressions
103+
* @see <a href="https://maven.apache.org/maven-ci-friendly.html">CI-Friendly Versions</a>
104+
*/
105+
private static final List<String> CI_FRIENDLY_PROPERTIES = Arrays.asList("revision", "sha1", "changelist");
106+
92107
private long startTime = -1 * 1000;
93108

94109
protected AbstractRewritePomsPhase(
@@ -266,7 +281,7 @@ private void transformDocument(
266281

267282
Properties properties = modelTarget.getProperties();
268283

269-
String parentVersion = rewriteParent(project, modelTarget, releaseDescriptor, simulate);
284+
String parentVersion = rewriteParent(project, modelTarget, result, releaseDescriptor, simulate);
270285

271286
String projectId = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
272287

@@ -440,8 +455,29 @@ private void rewriteVersion(
440455
modelTarget.setVersion(version);
441456
}
442457

458+
/**
459+
* Extracts the Maven property name from a given expression.
460+
* @param expression the expression
461+
* @return either {@code null} if value is no expression otherwise the property referenced in the expression
462+
*/
463+
public static String extractPropertyFromExpression(String expression) {
464+
Matcher matcher = EXPRESSION_PATTERN.matcher(expression);
465+
if (!matcher.matches()) {
466+
return null;
467+
}
468+
return matcher.group(1);
469+
}
470+
471+
public static boolean isCiFriendlyVersion(String version) {
472+
return CI_FRIENDLY_PROPERTIES.contains(extractPropertyFromExpression(version));
473+
}
474+
443475
private String rewriteParent(
444-
MavenProject project, Model targetModel, ReleaseDescriptor releaseDescriptor, boolean simulate)
476+
MavenProject project,
477+
Model targetModel,
478+
ReleaseResult result,
479+
ReleaseDescriptor releaseDescriptor,
480+
boolean simulate)
445481
throws ReleaseFailureException {
446482
String parentVersion = null;
447483
if (project.hasParent()) {
@@ -458,7 +494,13 @@ private String rewriteParent(
458494
throw new ReleaseFailureException("Version for parent '" + parent.getName() + "' was not mapped");
459495
}
460496
} else {
461-
targetModel.getParent().setVersion(parentVersion);
497+
if (!isCiFriendlyVersion(targetModel.getParent().getVersion())) {
498+
targetModel.getParent().setVersion(parentVersion);
499+
} else {
500+
logInfo(
501+
result,
502+
" Ignoring parent version update for CI friendly expression " + parent.getVersion());
503+
}
462504
}
463505
}
464506
return parentVersion;
@@ -521,61 +563,73 @@ private void rewriteArtifactVersions(
521563
if (rawVersion.equals(originalVersion)) {
522564
logInfo(result, " Updating " + artifactId + " to " + mappedVersion);
523565
coordinate.setVersion(mappedVersion);
524-
} else if (rawVersion.matches("\\$\\{.+\\}")) {
525-
String expression = rawVersion.substring(2, rawVersion.length() - 1);
526-
527-
if (expression.startsWith("project.")
528-
|| expression.startsWith("pom.")
529-
|| "version".equals(expression)) {
530-
if (!mappedVersion.equals(getNextVersion(releaseDescriptor, projectId))) {
531-
logInfo(result, " Updating " + artifactId + " to " + mappedVersion);
532-
coordinate.setVersion(mappedVersion);
533-
} else {
534-
logInfo(result, " Ignoring artifact version update for expression " + rawVersion);
535-
}
536-
} else if (properties != null) {
537-
// version is an expression, check for properties to update instead
538-
539-
String propertyValue = properties.getProperty(expression);
540-
541-
if (propertyValue != null) {
542-
if (propertyValue.equals(originalVersion)) {
543-
logInfo(result, " Updating " + rawVersion + " to " + mappedVersion);
544-
// change the property only if the property is the same as what's in the reactor
545-
properties.setProperty(expression, mappedVersion);
546-
} else if (mappedVersion.equals(propertyValue)) {
547-
// this property may have been updated during processing a sibling.
548-
logInfo(
549-
result,
550-
" Ignoring artifact version update for expression " + rawVersion
551-
+ " because it is already updated");
552-
} else if (!mappedVersion.equals(rawVersion)) {
553-
// WARNING: ${pom.*} prefix support and ${version} is about to be dropped in mvn4!
554-
// https://issues.apache.org/jira/browse/MNG-7404
555-
// https://issues.apache.org/jira/browse/MNG-7244
556-
if (mappedVersion.matches("\\$\\{project.+\\}")
557-
|| mappedVersion.matches("\\$\\{pom.+\\}")
558-
|| "${version}".equals(mappedVersion)) {
566+
} else {
567+
String property = extractPropertyFromExpression(rawVersion);
568+
if (property != null) {
569+
if (property.startsWith("project.")
570+
|| property.startsWith("pom.")
571+
|| "version".equals(property)) {
572+
if (!mappedVersion.equals(getNextVersion(releaseDescriptor, projectId))) {
573+
logInfo(result, " Updating " + artifactId + " to " + mappedVersion);
574+
coordinate.setVersion(mappedVersion);
575+
} else {
576+
logInfo(result, " Ignoring artifact version update for expression " + rawVersion);
577+
}
578+
} else if (properties != null) {
579+
// version is an expression, check for properties to update instead
580+
String propertyValue = properties.getProperty(property);
581+
if (propertyValue != null) {
582+
if (propertyValue.equals(originalVersion)) {
583+
logInfo(result, " Updating " + rawVersion + " to " + mappedVersion);
584+
// change the property only if the property is the same as what's in the reactor
585+
properties.setProperty(property, mappedVersion);
586+
} else if (mappedVersion.equals(propertyValue)) {
587+
// this property may have been updated during processing a sibling.
559588
logInfo(
560589
result,
561-
" Ignoring artifact version update for expression " + mappedVersion);
562-
// ignore... we cannot update this expression
590+
" Ignoring artifact version update for expression " + rawVersion
591+
+ " because it is already updated");
592+
} else if (!mappedVersion.equals(rawVersion)) {
593+
// WARNING: ${pom.*} prefix support and ${version} is about to be dropped in mvn4!
594+
// https://issues.apache.org/jira/browse/MNG-7404
595+
// https://issues.apache.org/jira/browse/MNG-7244
596+
if (mappedVersion.matches("\\$\\{project.+\\}")
597+
|| mappedVersion.matches("\\$\\{pom.+\\}")
598+
|| "${version}".equals(mappedVersion)) {
599+
logInfo(
600+
result,
601+
" Ignoring artifact version update for expression " + mappedVersion);
602+
// ignore... we cannot update this expression
603+
} else {
604+
// the value of the expression conflicts with what the user wanted to release
605+
throw new ReleaseFailureException("The artifact (" + key + ") requires a "
606+
+ "different version (" + mappedVersion + ") than what is found ("
607+
+ propertyValue + ") for the expression (" + rawVersion + ") in the "
608+
+ "project (" + projectId + ").");
609+
}
610+
}
611+
} else {
612+
if (CI_FRIENDLY_PROPERTIES.contains(property)) {
613+
logInfo(
614+
result,
615+
" Ignoring artifact version update for CI friendly expression "
616+
+ rawVersion);
563617
} else {
564-
// the value of the expression conflicts with what the user wanted to release
565-
throw new ReleaseFailureException("The artifact (" + key + ") requires a "
566-
+ "different version (" + mappedVersion + ") than what is found ("
567-
+ propertyValue + ") for the expression (" + expression + ") in the "
568-
+ "project (" + projectId + ").");
618+
// the expression used to define the version of this artifact may be inherited
619+
// TODO needs a better error message, what pom? what dependency?
620+
throw new ReleaseFailureException(
621+
"Could not find property resolving version expression: " + rawVersion);
569622
}
570623
}
571624
} else {
572625
// the expression used to define the version of this artifact may be inherited
573626
// TODO needs a better error message, what pom? what dependency?
574-
throw new ReleaseFailureException("The version could not be updated: " + rawVersion);
627+
throw new ReleaseFailureException(
628+
"Could not find properties resolving version expression : " + rawVersion);
575629
}
630+
} else {
631+
// different/previous version not related to current release
576632
}
577-
} else {
578-
// different/previous version not related to current release
579633
}
580634
} else if (resolvedSnapshotVersion != null) {
581635
logInfo(result, " Updating " + artifactId + " to " + resolvedSnapshotVersion);

maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom2/JDomModel.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.apache.maven.model.Profile;
3232
import org.apache.maven.model.Reporting;
3333
import org.apache.maven.model.Scm;
34+
import org.apache.maven.shared.release.phase.AbstractRewritePomsPhase;
3435
import org.jdom2.Document;
3536
import org.jdom2.Element;
3637
import org.jdom2.Text;
@@ -178,7 +179,9 @@ public void setVersion(String version) {
178179
}
179180

180181
if (versionElement == null) {
181-
if (!version.equals(parentVersion)) {
182+
// never add version when parent references CI friendly property
183+
if (!(parentVersion != null && AbstractRewritePomsPhase.isCiFriendlyVersion(parentVersion))
184+
&& !version.equals(parentVersion)) {
182185
// we will add this after artifactId, since it was missing but different from the inherited version
183186
Element artifactIdElement = project.getChild("artifactId", project.getNamespace());
184187
int index = project.indexOf(artifactIdElement);
@@ -189,7 +192,17 @@ public void setVersion(String version) {
189192
project.addContent(index + 2, versionElement);
190193
}
191194
} else {
192-
JDomUtils.rewriteValue(versionElement, version);
195+
if (AbstractRewritePomsPhase.isCiFriendlyVersion(versionElement.getTextNormalize())) {
196+
// try to rewrite property if CI friendly expression is used
197+
String ciFriendlyPropertyName =
198+
AbstractRewritePomsPhase.extractPropertyFromExpression(versionElement.getTextNormalize());
199+
Properties properties = getProperties();
200+
if (properties != null) {
201+
properties.computeIfPresent(ciFriendlyPropertyName, (k, v) -> version);
202+
}
203+
} else {
204+
JDomUtils.rewriteValue(versionElement, version);
205+
}
193206
}
194207
}
195208
}

maven-release-manager/src/main/java/org/apache/maven/shared/release/transform/jdom2/JDomParent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public JDomParent(Element parent) {
4141

4242
@Override
4343
public String getVersion() {
44-
throw new UnsupportedOperationException();
44+
return parent.getChildText("version", parent.getNamespace());
4545
}
4646

4747
@Override

maven-release-manager/src/test/java/org/apache/maven/shared/release/phase/RewritePomsForReleasePhaseTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,20 @@ public void testRewritePomWithParentAndProperties() throws Exception {
320320
assertTrue(comparePomFiles(reactorProjects));
321321
}
322322

323+
// MRELEASE-1109
324+
@Test
325+
public void testRewritePomWithCiFriendlyReactor() throws Exception {
326+
List<MavenProject> reactorProjects = createReactorProjects("pom-with-parent-and-cifriendly-expressions");
327+
328+
ReleaseDescriptorBuilder builder =
329+
createDescriptorFromProjects(reactorProjects, "pom-with-parent-and-cifriendly-expressions");
330+
builder.addReleaseVersion("groupId:artifactId", NEXT_VERSION);
331+
builder.addReleaseVersion("groupId:subproject1", NEXT_VERSION);
332+
phase.execute(ReleaseUtils.buildReleaseDescriptor(builder), new DefaultReleaseEnvironment(), reactorProjects);
333+
334+
assertTrue(comparePomFiles(reactorProjects));
335+
}
336+
323337
// MRELEASE-311
324338
@Test
325339
public void testRewritePomWithDependencyPropertyCoordinate() throws Exception {

maven-release-manager/src/test/java/org/apache/maven/shared/release/transform/jdom2/JDomModelTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,14 @@ public void testSetVersion() throws Exception {
6868
model.setVersion(null);
6969
assertNull(model.getVersion());
7070

71-
// inherit from parent
71+
// inherit from parent via CI friendly
72+
content = "<project><parent><version>${revision}</version></parent></project>";
73+
projectElm = builder.build(new StringReader(content)).getRootElement();
74+
model = new JDomModel(projectElm);
75+
assertNull(model.getVersion());
76+
model.setVersion("PARENT_VERSION");
77+
assertNull(getVersion(projectElm));
78+
7279
// this business logic might need to moved.
7380
content = "<project><parent><version>PARENT_VERSION</version></parent></project>";
7481
projectElm = builder.build(new StringReader(content)).getRootElement();

maven-release-manager/src/test/java/org/apache/maven/shared/release/transform/jdom2/JDomParentTest.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
*/
1919
package org.apache.maven.shared.release.transform.jdom2;
2020

21+
import java.io.IOException;
2122
import java.io.StringReader;
2223

2324
import org.jdom2.Element;
25+
import org.jdom2.JDOMException;
2426
import org.jdom2.input.SAXBuilder;
2527
import org.junit.Test;
2628

@@ -44,9 +46,12 @@ public void testGetRelativePath() {
4446
new JDomParent(null).getRelativePath();
4547
}
4648

47-
@Test(expected = UnsupportedOperationException.class)
48-
public void testGetVersion() {
49-
new JDomParent(null).getVersion();
49+
@Test
50+
public void testGetVersion() throws JDOMException, IOException {
51+
String content = "<parent><version>1.0</version></parent>";
52+
Element parentElm = builder.build(new StringReader(content)).getRootElement();
53+
54+
assertEquals("1.0", new JDomParent(parentElm).getVersion());
5055
}
5156

5257
@Test(expected = UnsupportedOperationException.class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
~ Copyright 2005-2006 The Apache Software Foundation.
5+
~
6+
~ Licensed under the Apache License, Version 2.0 (the "License");
7+
~ you may not use this file except in compliance with the License.
8+
~ 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, software
13+
~ distributed under the License is distributed on an "AS IS" BASIS,
14+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
~ See the License for the specific language governing permissions and
16+
~ limitations under the License.
17+
-->
18+
19+
<project>
20+
<modelVersion>4.0.0</modelVersion>
21+
<groupId>groupId</groupId>
22+
<artifactId>artifactId</artifactId>
23+
<version>${revision}</version>
24+
<packaging>pom</packaging>
25+
26+
<scm>
27+
<connection>scm:svn:file://localhost/tmp/scm-repo/tags/release-label</connection>
28+
<developerConnection>scm:svn:file://localhost/tmp/scm-repo/tags/release-label</developerConnection>
29+
<url>file://localhost/tmp/scm-repo/tags/release-label</url>
30+
</scm>
31+
32+
<properties>
33+
<revision>1.0-SNAPSHOT</revision>
34+
</properties>
35+
36+
<modules>
37+
<module>subproject1</module>
38+
</modules>
39+
</project>

0 commit comments

Comments
 (0)