Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/allow dependency snapshots #39

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
18 changes: 11 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

<groupId>com.github.danielflower.mavenplugins</groupId>
<artifactId>multi-module-maven-release-plugin</artifactId>
<version>3.0-SNAPSHOT</version> <!-- When changing also update scaffolding.TestProject.PLUGIN_VERSION_FOR_TESTS and add to src/site/markdown/changelog.md -->

<version>3.1-SNAPSHOT</version> <!-- When changing also update scaffolding.TestProject.PLUGIN_VERSION_FOR_TESTS and add to src/site/markdown/changelog.md -->
<name>The Multi Module Maven Release Plugin</name>
<description>A maven release plugin built for multi-maven-module git repositories allowing continuous deployment
</description>
Expand Down Expand Up @@ -78,6 +77,16 @@
<version>${jsch.agentproxy.version}</version>
</dependency>

<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
Expand All @@ -98,11 +107,6 @@
<artifactId>maven-settings</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-settings-builder</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.github.danielflower.mavenplugins.release;

import java.io.File;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
Expand All @@ -14,14 +16,37 @@
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.WriterFactory;

import java.io.File;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PomUpdater {

private final ArtifactFactory artifactFactory;
private final ArtifactMetadataSource artifactMetadataSource;
private final ArtifactRepository localRepository;
private final List<ArtifactRepository> remoteArtifactRepositories;
private final Log log;
private final Reactor reactor;
private final List<String> resolveSnapshots;

public PomUpdater(Log log, Reactor reactor) {
public PomUpdater(Log log, Reactor reactor, ArtifactFactory artifactFactory,
ArtifactMetadataSource artifactMetadataSource, ArtifactRepository localRepository,
List<ArtifactRepository> remoteArtifactRepositories,
List<String> resolveSnapshots) {
this.log = log;
this.reactor = reactor;
this.artifactFactory = artifactFactory;
this.artifactMetadataSource = artifactMetadataSource;
this.localRepository = localRepository;
this.remoteArtifactRepositories = remoteArtifactRepositories;
this.resolveSnapshots = resolveSnapshots;
}

public UpdateResult updateVersion() {
Expand Down Expand Up @@ -70,6 +95,21 @@ public boolean success() {
}
}

/**
* Check whether the given dependencySnapshot is matched by the resolveSnapshotPattern (regular expression).
*
* @param dependencySnapshot a dependency string in a gradle like notation, i.e., groupId:artifactId:version
* @param resolveSnapshotPattern a regular expression pattern which is used in the plugin configuration, e.g.,
* ^org\.slf4j:slf4j-api:1\.7\..*
* @return if the dependency string matches the regex
*/
protected static boolean snapshotResolves(String dependencySnapshot, String resolveSnapshotPattern) {
Pattern resolveSnapshotPatternAsPattern = Pattern.compile(resolveSnapshotPattern);
Matcher matcher = resolveSnapshotPatternAsPattern.matcher(dependencySnapshot);

return matcher.matches();
}

private List<String> alterModel(MavenProject project, String newVersion) {
Model originalModel = project.getOriginalModel();
originalModel.setVersion(newVersion);
Expand Down Expand Up @@ -97,7 +137,27 @@ private List<String> alterModel(MavenProject project, String newVersion) {
dependency.setVersion(dependencyBeingReleased.getVersionToDependOn());
log.debug(" Dependency on " + dependencyBeingReleased.getArtifactId() + " rewritten to version " + dependencyBeingReleased.getVersionToDependOn());
} catch (UnresolvedSnapshotDependencyException e) {
errors.add(searchingFrom + " references dependency " + e.artifactId + " " + e.version);
boolean resolveSnapshotFound = false;
if (null != resolveSnapshots) {
String dependencySnapshot = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + version;
log.debug(" Trying to resolve snapshot: '" + dependencySnapshot + "' with ...");
for (String resolveSnapshot : resolveSnapshots) {
log.debug(" ...: '" + resolveSnapshot + "'");
if (snapshotResolves(dependencySnapshot, resolveSnapshot)) {
String resolvedVersion = resolveSnapshotDependency (dependency);
log.debug(" Resolving snapshot dependency: '" + dependencySnapshot + "' to '" + resolvedVersion + "'");
if (null != resolvedVersion) {
resolveSnapshotFound = true;
dependency.setVersion(resolvedVersion);
}
// Break loop anyways: If release was not found, it is an error ...
break;
}
}
}
if (!resolveSnapshotFound) {
errors.add(searchingFrom + " references dependency " + e.artifactId + " " + e.version);
}
}
}else
log.debug(" Dependency on " + dependency.getArtifactId() + " kept at version " + dependency.getVersion());
Expand All @@ -112,14 +172,70 @@ private List<String> alterModel(MavenProject project, String newVersion) {
}
return errors;
}

private String resolveVersion(String version, Properties projectProperties) {
if (version != null && version.startsWith("${")) {
return projectProperties.getProperty(version.replace("${", "").replace("}", ""), version);
}
return version;
}

private String resolveSnapshotDependency (Dependency dependency)
{
Artifact artifact = createDependencyArtifact(dependency);
log.debug ("Retrieving versions for artifact: " + artifact);
try {
List<ArtifactVersion> versions =
artifactMetadataSource.retrieveAvailableVersions( artifact, localRepository, remoteArtifactRepositories);
if (versions.size() == 0) {
log.error("Could not retrieve versions for artifact: " + artifact);
return null;
}
Collections.sort(versions, new Comparator<ArtifactVersion>() {
@Override
public int compare(ArtifactVersion o1, ArtifactVersion o2) {
return o1.compareTo(o2);
}
});
ArtifactVersion latestVersion = versions.get(versions.size() - 1);
log.debug("Using version '" + latestVersion + "' for '" + artifact + "'");
return latestVersion.toString();
} catch (ArtifactMetadataRetrievalException e) {
log.error("Could not retrieve versions for artifact: " + artifact, e);
return null;
}
}

// Stolen from Versions Maven Plugin
private Artifact createDependencyArtifact(String groupId, String artifactId, VersionRange versionRange, String type,
String classifier, String scope )
{
return artifactFactory.createDependencyArtifact( groupId, artifactId, versionRange, type, classifier, scope );
}

private Artifact createDependencyArtifact( Dependency dependency )
{
String strippedVersion = dependency.getVersion().replace("-SNAPSHOT", ".0");
String versionRangeSpec = "[" + strippedVersion + ",]";
VersionRange versionRange = null;
try {
versionRange = VersionRange.createFromVersionSpec(versionRangeSpec);
} catch (InvalidVersionSpecificationException e) {
log.error ("Could not resolve version range '" + versionRangeSpec + "'");
return null;
}

return createDependencyArtifact(
dependency.getGroupId(),
dependency.getArtifactId(),
versionRange,
dependency.getType(),
dependency.getClassifier(),
dependency.getScope()
);
}


private static boolean isMultiModuleReleasePlugin(Plugin plugin) {
return plugin.getGroupId().equals("com.github.danielflower.mavenplugins") && plugin.getArtifactId().equals("multi-module-maven-release-plugin");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package com.github.danielflower.mavenplugins.release;

import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.settings.io.DefaultSettingsWriter;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.eclipse.jgit.api.errors.GitAPIException;

import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
Expand All @@ -31,6 +41,18 @@
)
public class ReleaseMojo extends BaseMojo {

@Inject
private ArtifactFactory artifactFactory;

@Inject
private ArtifactMetadataSource artifactMetadataSource;

@Parameter(property="localRepository", required = true, readonly = true)
private ArtifactRepository localRepository;

@Parameter(property="project.remoteArtifactRepositories", required = true, readonly = true)
private List<ArtifactRepository> remoteArtifactRepositories;

/**
* <p>
* The goals to run against the project during a release. By default this is "deploy" which
Expand Down Expand Up @@ -72,7 +94,7 @@ public class ReleaseMojo extends BaseMojo {
*/
@Parameter(alias = "skipTests", defaultValue = "false", property = "skipTests")
private boolean skipTests;

/**
* Specifies a custom, user specific Maven settings file to be used during the release build.
*
Expand All @@ -92,18 +114,23 @@ public class ReleaseMojo extends BaseMojo {
*/
@Parameter(alias = "globalSettings")
private File globalSettings;

/**
* Push tags to remote repository as they are created.
*/
@Parameter(alias = "pushTags", defaultValue="true", property="push")
private boolean pushTags;



@Parameter(property = "resolveSnapshots")
private List<String> resolveSnapshots;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
Log log = getLog();

log.info ("Resolving snapshots: " + resolveSnapshots);

try {
configureJsch(log);

Expand Down Expand Up @@ -202,8 +229,8 @@ private static void revertChanges(Log log, LocalGitRepo repo, List<File> changed
}
}

private static List<File> updatePomsAndReturnChangedFiles(Log log, LocalGitRepo repo, Reactor reactor) throws MojoExecutionException, ValidationException {
PomUpdater pomUpdater = new PomUpdater(log, reactor);
private List<File> updatePomsAndReturnChangedFiles(Log log, LocalGitRepo repo, Reactor reactor) throws MojoExecutionException, ValidationException {
PomUpdater pomUpdater = new PomUpdater(log, reactor, artifactFactory, artifactMetadataSource, localRepository, remoteArtifactRepositories, resolveSnapshots);
PomUpdater.UpdateResult result = pomUpdater.updateVersion();
if (!result.success()) {
log.info("Going to revert changes because there was an error.");
Expand Down Expand Up @@ -259,4 +286,61 @@ static List<AnnotatedTag> figureOutTagNamesAndThrowIfAlreadyExists(List<Releasab
return tags;
}

private void runMavenBuild(Reactor reactor) throws MojoExecutionException {
InvocationRequest request = new DefaultInvocationRequest();
request.setInteractive(false);

if (goals == null) {
goals = new ArrayList<String>();
goals.add("deploy");
}
if (skipTests) {
goals.add("-DskipTests=true");
}
request.setShowErrors(true);
request.setDebug(getLog().isDebugEnabled());
request.setGoals(goals);
List<String> profiles = profilesToActivate();
request.setProfiles(profiles);

request.setAlsoMake(true);
List<String> changedModules = new ArrayList<String>();
for (ReleasableModule releasableModule : reactor.getModulesInBuildOrder()) {
String modulePath = releasableModule.getRelativePathToModule();
boolean userExplicitlyWantsThisToBeReleased = modulesToRelease.contains(modulePath);
boolean userImplicitlyWantsThisToBeReleased = modulesToRelease == null || modulesToRelease.size() == 0;
if (userExplicitlyWantsThisToBeReleased || (userImplicitlyWantsThisToBeReleased && releasableModule.willBeReleased())) {
changedModules.add(modulePath);
}
}
request.setProjects(changedModules);

String profilesInfo = (profiles.size() == 0) ? "no profiles activated" : "profiles " + profiles;

getLog().info("About to run mvn " + goals + " with " + profilesInfo);

Invoker invoker = new DefaultInvoker();
try {
InvocationResult result = invoker.execute(request);
if (result.getExitCode() != 0) {
throw new MojoExecutionException("Maven execution returned code " + result.getExitCode());
}
} catch (MavenInvocationException e) {
throw new MojoExecutionException("Failed to build artifact", e);
}
}

private List<String> profilesToActivate() {
List<String> profiles = new ArrayList<String>();
if (releaseProfiles != null) {
for (String releaseProfile : releaseProfiles) {
profiles.add(releaseProfile);
}
}
for (Object activatedProfile : project.getActiveProfiles()) {
profiles.add(((org.apache.maven.model.Profile) activatedProfile).getId());
}
return profiles;
}

}
6 changes: 5 additions & 1 deletion src/site/markdown/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
---------

### 3.1.0

* Enable snapshot dependencies where dependencies are managed by _releaser_ itself (or in a similar manner), cf. [#38](https://github.com/danielflower/multi-module-maven-release-plugin/issues/38)

### 3.0.0

* Update JGit to 4.7.0 to ensure changed files are detected for projects
Expand Down Expand Up @@ -114,4 +118,4 @@ generated version number would clash with an existing tag.

## 1.0.0

First stable release.
First stable release.
2 changes: 2 additions & 0 deletions src/site/markdown/index.md.vm
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ version of a module was released
* A module is only released if there are changes to it
* The release version of the pom is not committed back to the repository
* Tests are run once by default (or optionally not at all)
* You can have snapshot dependencies when they can be resolved to a certain release, e.g., if 1.3-SNAPSHOT can be
resolved to 1.3.4.

Prerequisites
-------------
Expand Down
Loading