Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion maven-wrapper-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ under the License.
</prerequisites>

<properties>
<version.mockito>4.11.0</version.mockito>
<version.mockito>5.23.0</version.mockito>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumped to use mockStatic and mockConstruction

</properties>

<dependencies>
Expand Down Expand Up @@ -125,6 +125,12 @@ under the License.
<version>${version.mockito}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
66 changes: 66 additions & 0 deletions maven-wrapper-plugin/src/it/projects/mavenversion_range/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version='1.0' encoding='UTF-8'?>

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.apache.maven.plugins.it.wrapper</groupId>
<artifactId>mavenversion_range</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<properties>
<cmd></cmd>
</properties>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>@version.exec-maven-plugin@</version>
<configuration>
<executable>mvnw${cmd}</executable>
<arguments>
<argument>-v</argument>
</arguments>
<environmentVariables>
<MVNW_VERBOSE>true</MVNW_VERBOSE>
</environmentVariables>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<profiles>
<profile>
<id>windows</id>
<activation>
<os><family>windows</family></os>
</activation>
<properties>
<cmd>.cmd</cmd>
</properties>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

maven=[3.0,3.10-rc\)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

assert new File(basedir,'mvnw').exists()
assert new File(basedir,'mvnw.cmd').exists()
assert !(new File( basedir, 'mvnwDebug' ).exists())
assert !(new File( basedir, 'mvnwDebug.cmd' ).exists())

def wrapperProperties = new File(basedir,'.mvn/wrapper/maven-wrapper.properties')
assert wrapperProperties.exists()

Properties props = new Properties()
wrapperProperties.withInputStream {
props.load(it)
}

// Assert that distribution URL was updated to the highest version automatically resolved
// in the specified range: [3.0,3.10-rc)
assert props.distributionUrl.endsWith("/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip")

def log = new File(basedir, 'build.log').text
assert log.contains('Apache Maven 3.9.16')
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.maven.Maven;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
Expand All @@ -48,7 +50,11 @@
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.version.Version;

import static java.util.Comparator.reverseOrder;
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;

/**
Expand All @@ -69,9 +75,11 @@ public class WrapperMojo extends AbstractMojo {

/**
* The version of Maven to require, default value is the Runtime version of Maven.
* Can be any valid release above 2.0.9
* Can be any valid release above 2.0.9.
* Automatic version resolution is supported via {@link VersionRange} such as {@code [3.0,4.0-alpha)}.
*
* @since 3.0.0
* @see <a href="https://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification">Dependency Version Requirement Specification</a>
*/
@Parameter(property = "maven")
private String mavenVersion;
Expand Down Expand Up @@ -211,7 +219,7 @@ public void execute() throws MojoExecutionException {
+ " cannot work with mvnd, please set type to '" + TYPE_ONLY_SCRIPT + "'.");
}

mavenVersion = getVersion(mavenVersion, Maven.class, "org.apache.maven/maven-core");
mavenVersion = resolveMavenVersion(getVersion(mavenVersion, Maven.class, "org.apache.maven/maven-core"));
String wrapperVersion = getVersion(null, this.getClass(), "org.apache.maven.plugins/maven-wrapper-plugin");

final Artifact artifact = downloadWrapperDistribution(wrapperVersion);
Expand Down Expand Up @@ -376,6 +384,36 @@ private String getVersion(String defaultVersion, Class<?> clazz, String path) {
return version;
}

/**
* Resolves the actual Maven version to download, given the range in {@link WrapperMojo#mavenVersion}.
* If the requested range could not be parsed, the version provided as input is returned.
*
* @param version the Maven version range
*
* @return the highest release version according to the provided range
* @see <a href="https://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification">Dependency Version Requirement Specification</a>
*/
String resolveMavenVersion(String version) {
try {
Artifact artifact = new DefaultArtifact("org.apache.maven:apache-maven:" + version);
VersionRangeRequest request = new VersionRangeRequest(
artifact, session.getCurrentProject().getRemotePluginRepositories(), "wrapper");

List<Version> versions = repositorySystem
.resolveVersionRange(repositorySystemSession, request)
.getVersions();
versions.sort(reverseOrder());

return versions.stream()
.map(Version::toString)
.filter(v -> !artifact.setVersion(v).isSnapshot())
.findFirst()
.orElse(version);
} catch (VersionRangeResolutionException e) {
return version;
}
}

/**
* Determine the repository URL to download Wrapper and Maven from.
*/
Expand Down
8 changes: 6 additions & 2 deletions maven-wrapper-plugin/src/site/markdown/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ You can still use `-Dtype={type}` to change the distribution type for an existin
Maven Version
-------------
By default the plugin will assume the same version as the Maven runtime (calling `mvn -v`). But you can pick a different version.
You can call `mvn wrapper:wrapper -Dmaven=x`, where `x` is any valid Apache Maven Release (see [Maven Central](https://central.sonatype.com/artifact/org.apache.maven/apache-maven/versions)).
Another option is to adjust the `distributionUrl` in `.mvn/wrapper/maven-wrapper.properties`.
You can call `mvn wrapper:wrapper -Dmaven=x`, where `x` is any valid Apache Maven Release (see [Maven Central](https://central.sonatype.com/artifact/org.apache.maven/apache-maven/versions))
or a version range (see [Dependency Version Requirement Specification](https://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification)).
In the latter case, mind that round brackets must be escaped with a backslash, as in `-Dmaven=[3.0,4.0\)`,
or the whole argument must be quoted: `-Dmaven="[3.0,4.0)"`.

Another option is to adjust the `distributionUrl` in `.mvn/wrapper/maven-wrapper.properties` with the specific version.

Debugging
---------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,34 @@
*/
package org.apache.maven.plugins.wrapper;

import java.util.List;
import java.util.stream.Stream;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.version.Version;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
import org.mockito.junit.jupiter.MockitoExtension;

import static java.util.Comparator.reverseOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
Expand All @@ -40,6 +56,27 @@ class WrapperMojoTest {
@Mock
private RepositorySystemSession repositorySystemSession;

@Mock
private MavenSession session;

@Mock
private MavenProject project;

@Mock
private List<RemoteRepository> remoteRepositories;

@Mock
private VersionRangeResult result;

@Mock
private Version version1;

@Mock
private Version version2;

@Mock
private List<Version> versions;

@InjectMocks
private WrapperMojo wrapperMojo;

Expand All @@ -49,6 +86,76 @@ void setupMocks() {
.then(i -> i.getArguments()[1]);
}

@Test
void resolveMavenVersion() {
String versionRange = "versionRange";
String resolvedVersion1 = "resolvedVersion1";
String resolvedVersion2 = "resolvedVersion2";

try (MockedConstruction<DefaultArtifact> artifactMockedConstruction = mockConstruction((artifact, context) -> {
assertEquals(1, context.arguments().size());
assertEquals(
"org.apache.maven:apache-maven:" + versionRange,
context.arguments().get(0));

when(artifact.setVersion(resolvedVersion1)).thenReturn(artifact);
when(artifact.setVersion(resolvedVersion2)).thenReturn(artifact);
when(artifact.isSnapshot()).thenReturn(true, false);
});
MockedConstruction<VersionRangeRequest> ignored = mockConstruction((request, context) -> {
assertEquals(3, context.arguments().size());
assertEquals(
artifactMockedConstruction.constructed().get(0),
context.arguments().get(0));
assertEquals(remoteRepositories, context.arguments().get(1));
assertEquals("wrapper", context.arguments().get(2));

when(repositorySystem.resolveVersionRange(repositorySystemSession, request))
.thenReturn(result);
})) {
when(session.getCurrentProject()).thenReturn(project);
when(project.getRemotePluginRepositories()).thenReturn(remoteRepositories);
when(result.getVersions()).thenReturn(versions);
when(versions.stream()).thenReturn(Stream.of(version1, version2));
when(version1.toString()).thenReturn(resolvedVersion1);
when(version2.toString()).thenReturn(resolvedVersion2);

assertEquals(resolvedVersion2, wrapperMojo.resolveMavenVersion(versionRange));

verify(versions).sort(reverseOrder());
}
}

@Test
void resolveMavenVersionException() {
String versionRange = "versionRange";

try (MockedConstruction<DefaultArtifact> artifactMockedConstruction = mockConstruction((artifact, context) -> {
assertEquals(1, context.arguments().size());
assertEquals(
"org.apache.maven:apache-maven:" + versionRange,
context.arguments().get(0));

verifyNoInteractions(artifact);
});
MockedConstruction<VersionRangeRequest> ignored = mockConstruction((request, context) -> {
assertEquals(3, context.arguments().size());
assertEquals(
artifactMockedConstruction.constructed().get(0),
context.arguments().get(0));
assertEquals(remoteRepositories, context.arguments().get(1));
assertEquals("wrapper", context.arguments().get(2));

when(repositorySystem.resolveVersionRange(repositorySystemSession, request))
.thenThrow(VersionRangeResolutionException.class);
})) {
when(session.getCurrentProject()).thenReturn(project);
when(project.getRemotePluginRepositories()).thenReturn(remoteRepositories);

assertEquals(versionRange, wrapperMojo.resolveMavenVersion(versionRange));
}
}

@Test
void userSuppliedRepoUrlGetsTrailingSlashTrimmed() {
// when
Expand Down