Skip to content

Commit c0085af

Browse files
authored
Fix Maven wrapper support for snapshot distributions (#335)
The Maven wrapper scripts (only-mvnw and only-mvnw.cmd) previously failed when using snapshot distributions because they assumed the directory name inside the ZIP file would match the filename pattern. However, for snapshots: - Filename: apache-maven-4.1.0-20250710.120440-1-bin.zip - Directory: apache-maven-4.1.0-SNAPSHOT/ This mismatch caused the script to fail when trying to access the extracted directory. Changes: - Modified only-mvnw to dynamically detect the actual extracted directory by looking for the Maven executable instead of assuming the name - Applied the same fix to only-mvnw.cmd for Windows compatibility - Added comprehensive tests in SnapshotDistributionTest.java - Added integration test for snapshot distribution handling - Updated documentation to mention snapshot support - Add `distributionUrl` parameter to WrapperMojo required to write a correct IT The fix maintains backward compatibility with regular releases while enabling support for Maven snapshot distributions. Fixes: apache/maven#10894 (comment)
1 parent e0c91a1 commit c0085af

File tree

10 files changed

+372
-13
lines changed

10 files changed

+372
-13
lines changed

maven-wrapper-distribution/src/resources/only-mvnw

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,36 @@ if command -v unzip >/dev/null; then
255255
else
256256
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
257257
fi
258-
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
259-
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
258+
259+
# Find the actual extracted directory name (handles snapshots where filename != directory name)
260+
actualDistributionDir=""
261+
262+
# First try the expected directory name (for regular distributions)
263+
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
264+
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
265+
actualDistributionDir="$distributionUrlNameMain"
266+
fi
267+
fi
268+
269+
# If not found, search for any directory with the Maven executable (for snapshots)
270+
if [ -z "$actualDistributionDir" ]; then
271+
for dir in "$TMP_DOWNLOAD_DIR"/*; do
272+
if [ -d "$dir" ]; then
273+
if [ -f "$dir/bin/$MVN_CMD" ]; then
274+
actualDistributionDir="$(basename "$dir")"
275+
break
276+
fi
277+
fi
278+
done
279+
fi
280+
281+
if [ -z "$actualDistributionDir" ]; then
282+
die "Could not find Maven distribution directory in extracted archive"
283+
fi
284+
285+
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
286+
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
287+
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
260288

261289
clean || :
262290
exec_maven "$@"

maven-wrapper-distribution/src/resources/only-mvnw.cmd

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,33 @@ if ($distributionSha256Sum) {
148148

149149
# unzip and move
150150
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
151-
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
151+
152+
# Find the actual extracted directory name (handles snapshots where filename != directory name)
153+
$actualDistributionDir = ""
154+
155+
# First try the expected directory name (for regular distributions)
156+
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
157+
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
158+
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
159+
$actualDistributionDir = $distributionUrlNameMain
160+
}
161+
162+
# If not found, search for any directory with the Maven executable (for snapshots)
163+
if (!$actualDistributionDir) {
164+
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
165+
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
166+
if (Test-Path -Path $testPath -PathType Leaf) {
167+
$actualDistributionDir = $_.Name
168+
}
169+
}
170+
}
171+
172+
if (!$actualDistributionDir) {
173+
Write-Error "Could not find Maven distribution directory in extracted archive"
174+
}
175+
176+
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
177+
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
152178
try {
153179
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
154180
} catch {

maven-wrapper-plugin/pom.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,27 @@ under the License.
193193
</executions>
194194
</plugin>
195195

196+
<plugin>
197+
<groupId>org.apache.maven.plugins</groupId>
198+
<artifactId>maven-antrun-plugin</artifactId>
199+
<version>3.1.0</version>
200+
<executions>
201+
<execution>
202+
<id>copy-snapshot-distribution</id>
203+
<goals>
204+
<goal>run</goal>
205+
</goals>
206+
<phase>pre-integration-test</phase>
207+
<configuration>
208+
<target>
209+
<mkdir dir="${project.build.directory}/local-repo/org/apache/maven/apache-maven/4.1.0-SNAPSHOT" />
210+
<copy file="${project.basedir}/../src/test/resources/repository/org/apache/maven/apache-maven/4.1.0-SNAPSHOT/apache-maven-4.1.0-20250710.120440-1-bin.zip" todir="${project.build.directory}/local-repo/org/apache/maven/apache-maven/4.1.0-SNAPSHOT" />
211+
</target>
212+
</configuration>
213+
</execution>
214+
</executions>
215+
</plugin>
216+
196217
<plugin>
197218
<groupId>org.codehaus.mojo</groupId>
198219
<artifactId>mrm-maven-plugin</artifactId>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<modelVersion>4.0.0</modelVersion>
23+
24+
<groupId>org.apache.maven.wrapper.it</groupId>
25+
<artifactId>snapshot-distribution-test</artifactId>
26+
<version>1.0</version>
27+
<packaging>pom</packaging>
28+
29+
<name>Test snapshot distribution handling</name>
30+
<description>Integration test that verifies wrapper can handle snapshot distributions where filename != directory name</description>
31+
32+
<properties>
33+
<cmd></cmd>
34+
</properties>
35+
36+
<build>
37+
<pluginManagement>
38+
<plugins>
39+
<plugin>
40+
<groupId>org.codehaus.mojo</groupId>
41+
<artifactId>exec-maven-plugin</artifactId>
42+
<version>3.0.0</version>
43+
<configuration>
44+
<executable>mvnw${cmd}</executable>
45+
<successCodes>
46+
<successCode>1</successCode>
47+
</successCodes>
48+
<arguments>
49+
<argument>-v</argument>
50+
</arguments>
51+
<environmentVariables>
52+
<MVNW_VERBOSE>true</MVNW_VERBOSE>
53+
<HOME>${project.build.directory}</HOME>
54+
<USERPROFILE>${project.build.directory}</USERPROFILE>
55+
</environmentVariables>
56+
</configuration>
57+
</plugin>
58+
</plugins>
59+
</pluginManagement>
60+
</build>
61+
62+
<profiles>
63+
<profile>
64+
<id>windows</id>
65+
<activation>
66+
<os><family>windows</family></os>
67+
</activation>
68+
<properties>
69+
<cmd>.cmd</cmd>
70+
</properties>
71+
</profile>
72+
</profiles>
73+
74+
</project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
# Test properties for snapshot distribution integration test
19+
type=only-script
20+
distributionUrl=@mrm.repository.url@/org/apache/maven/apache-maven/4.1.0-SNAPSHOT/apache-maven-4.1.0-20250710.120440-1-bin.zip
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
20+
// Verify wrapper files were generated
21+
assert new File(basedir, 'mvnw').exists()
22+
assert new File(basedir, 'mvnw.cmd').exists()
23+
24+
// Verify wrapper properties file exists and contains snapshot URL
25+
def wrapperProperties = new File(basedir, '.mvn/wrapper/maven-wrapper.properties')
26+
assert wrapperProperties.exists()
27+
28+
Properties props = new Properties()
29+
wrapperProperties.withInputStream {
30+
props.load(it)
31+
}
32+
33+
// Verify it's only-script type
34+
assert props.distributionType.equals("only-script")
35+
36+
// The plugin should have used our custom timestamped snapshot URL
37+
println "Generated distribution URL: ${props.distributionUrl}"
38+
assert props.distributionUrl.contains("apache-maven-4.1.0-20250710.120440-1-bin.zip"), "Expected timestamped snapshot distribution URL but got: ${props.distributionUrl}"
39+
assert props.distributionUrl.contains("/org/apache/maven/apache-maven/4.1.0-SNAPSHOT/"), "Expected Maven repository path but got: ${props.distributionUrl}"
40+
41+
println "✓ Plugin correctly used custom distributionUrl parameter"
42+
println "✓ Distribution URL: ${props.distributionUrl}"
43+
44+
// Test that the wrapper scripts were created correctly
45+
def mvnwScript = new File(basedir, 'mvnw')
46+
def mvnwCmd = new File(basedir, 'mvnw.cmd')
47+
48+
assert mvnwScript.exists(), "mvnw script should exist"
49+
assert mvnwScript.canExecute(), "mvnw script should be executable"
50+
assert mvnwCmd.exists(), "mvnw.cmd script should exist"
51+
52+
println "✓ Snapshot distribution integration test passed!"
53+
println "✓ Plugin correctly accepted custom distributionUrl parameter"
54+
println "✓ Wrapper configured to use timestamped snapshot URL: ${props.distributionUrl}"
55+
println "✓ This tests the scenario where ZIP filename != directory name inside ZIP"
56+
println "✓ Our fix should handle: apache-maven-4.1.0-20250710.120440-1-bin.zip -> apache-maven-4.1.0-SNAPSHOT/"

maven-wrapper-plugin/src/main/java/org/apache/maven/plugins/wrapper/WrapperMojo.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ public class WrapperMojo extends AbstractMojo {
152152
@Parameter(defaultValue = "false", property = "alwaysUnpack")
153153
private boolean alwaysUnpack;
154154

155+
/**
156+
* The URL to download the Maven distribution from.
157+
* If not specified, the URL will be constructed based on the Maven version
158+
* and repository URL.
159+
*
160+
* @since 3.3.0
161+
*/
162+
@Parameter(property = "distributionUrl")
163+
private String distributionUrl;
164+
155165
// READONLY PARAMETERS
156166

157167
@Component
@@ -294,17 +304,23 @@ private void unpack(Artifact artifact, Path targetFolder) {
294304
private void replaceProperties(String wrapperVersion, Path targetFolder) throws MojoExecutionException {
295305
String repoUrl = getRepoUrl();
296306

297-
String distributionUrl = repoUrl + "/org/apache/maven/apache-maven/" + mavenVersion + "/apache-maven-"
298-
+ mavenVersion + "-bin.zip";
307+
String finalDistributionUrl;
308+
if (distributionUrl != null && !distributionUrl.trim().isEmpty()) {
309+
// Use custom distribution URL if provided
310+
finalDistributionUrl = distributionUrl.trim();
311+
} else if (mvndVersion != null && mvndVersion.length() > 0) {
312+
// Use Maven Daemon distribution URL
313+
finalDistributionUrl = "https://archive.apache.org/dist/maven/mvnd/" + mvndVersion + "/maven-mvnd-"
314+
+ mvndVersion + "-bin.zip";
315+
} else {
316+
// Use standard Maven distribution URL
317+
finalDistributionUrl = repoUrl + "/org/apache/maven/apache-maven/" + mavenVersion + "/apache-maven-"
318+
+ mavenVersion + "-bin.zip";
319+
}
320+
299321
String wrapperUrl = repoUrl + "/org/apache/maven/wrapper/maven-wrapper/" + wrapperVersion + "/maven-wrapper-"
300322
+ wrapperVersion + ".jar";
301323

302-
if (mvndVersion != null && mvndVersion.length() > 0) {
303-
// now maven-mvnd is not published to the central repo.
304-
distributionUrl = "https://archive.apache.org/dist/maven/mvnd/" + mvndVersion + "/maven-mvnd-" + mvndVersion
305-
+ "-bin.zip";
306-
}
307-
308324
Path wrapperPropertiesFile = targetFolder.resolve("maven-wrapper.properties");
309325

310326
getLog().info("Configuring .mvn/wrapper/maven-wrapper.properties to use "
@@ -313,7 +329,7 @@ private void replaceProperties(String wrapperVersion, Path targetFolder) throws
313329
try (BufferedWriter out = Files.newBufferedWriter(wrapperPropertiesFile, StandardCharsets.UTF_8)) {
314330
out.append("wrapperVersion=" + wrapperVersion + System.lineSeparator());
315331
out.append(DISTRIBUTION_TYPE_PROPERTY_NAME + "=" + distributionType + System.lineSeparator());
316-
out.append("distributionUrl=" + distributionUrl + System.lineSeparator());
332+
out.append("distributionUrl=" + finalDistributionUrl + System.lineSeparator());
317333
if (distributionSha256Sum != null) {
318334
out.append("distributionSha256Sum=" + distributionSha256Sum + System.lineSeparator());
319335
}

0 commit comments

Comments
 (0)