Skip to content

Commit

Permalink
[FIXED JENKINS-12735] Merge branch 'pull-542'
Browse files Browse the repository at this point in the history
Originally-Committed-As: a0c02400313cee84e06a960be29b8f43389ab274
  • Loading branch information
kohsuke committed Sep 1, 2012
2 parents 25e6abd + 34656ec commit 151a41d
Show file tree
Hide file tree
Showing 4 changed files with 372 additions and 56 deletions.
136 changes: 81 additions & 55 deletions src/main/java/hudson/maven/MavenModule.java
Expand Up @@ -23,6 +23,8 @@
*/
package hudson.maven;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import hudson.CopyOnWrite;
import hudson.Functions;
import hudson.Util;
Expand All @@ -45,7 +47,13 @@
import hudson.tasks.Publisher;
import hudson.util.AlternativeUiTextProvider;
import hudson.util.DescribableList;
import jenkins.model.Jenkins;
import org.apache.maven.project.MavenProject;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -58,15 +66,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import jenkins.model.Jenkins;

import org.apache.maven.project.MavenProject;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;

/**
* {@link Job} that builds projects based on Maven2.
*
Expand Down Expand Up @@ -105,7 +104,7 @@ public class MavenModule extends AbstractMavenProject<MavenModule,MavenBuild> im
private transient ModuleName moduleName;

/**
* @see documentation in {@link PomInfo#relativePath}
* @see PomInfo#relativePath
*/
private String relativePath;

Expand Down Expand Up @@ -423,15 +422,15 @@ protected void buildDependencyGraph(DependencyGraph graph) {
// to emulate the old behavior that tries to identify the upstream by ignoring the version.
// Do this by putting groupId:artifactId:UNKNOWN to the modules list, but
// ONLY if we find a such an old MavenModule in this Jenkins instance.
boolean hasDependenciesWithUnknownVersion = hasDependenciesWithUnknownVersion();
final boolean hasDependenciesWithUnknownVersion = hasDependenciesWithUnknownVersion();
if (data == null) {
Map<ModuleDependency,MavenModule> modules = new HashMap<ModuleDependency,MavenModule>();

for (MavenModule m : getAllMavenModules()) {
if(!m.isBuildable()) continue;
ModuleDependency moduleDependency = m.asDependency();
MavenModule old = modules.get(moduleDependency);
MavenModule relevant = chooseMoreRelevantModule(old, m, moduleDependency);
MavenModule relevant = chooseMoreRelevantModule(old, m);
modules.put(moduleDependency, relevant);
if (hasDependenciesWithUnknownVersion) {
modules.put(moduleDependency.withUnknownVersion(),relevant);
Expand All @@ -452,9 +451,6 @@ protected void buildDependencyGraph(DependencyGraph graph) {
}
}

// In case two modules with the same name are defined, modules in the same MavenModuleSet
// take precedence.

// Can lead to OOME, if remembered in the computational data and there are lot big multi-module projects
// TODO: try to use soft references to clean the heap when needed
Map<ModuleDependency,MavenModule> myParentsModules; // = data.modulesPerParent.get(getParent());
Expand All @@ -474,24 +470,40 @@ protected void buildDependencyGraph(DependencyGraph graph) {
//data.modulesPerParent.put(getParent(), myParentsModules);
//}

// if the build style is the aggregator build, define dependencies against project,
// not module.
AbstractProject<?, ?> dest = getParent().isAggregatorStyleBuild() ? getParent() : this;
//Create a map of groupId:artifact id keys to modules for faster look ups in findMatchingDependentModule
Multimap<ModuleName,ModuleDependency> mapModules = data.byName();

for (ModuleDependency d : dependencies) {
MavenModule src = myParentsModules.get(d);
MavenModule src;

// In case two modules with the same name are defined, modules in the same MavenModuleSet
// take precedence.
src = myParentsModules.get(d);

// otherwise we can pick the module with the highest version number (within the constraint that
// it satisfies 'd')
if (src==null) {
src = data.allModules.get(d);
Collection<ModuleDependency> candidates = mapModules.get(d.getName());
ModuleDependency winner = d.findHighestFrom(candidates);
src = data.allModules.get(winner);
}

if(src!=null) {
DependencyGraph.Dependency dep = new MavenModuleDependency(
src.getParent().isAggregatorStyleBuild() ? src.getParent() : src,dest);
DependencyGraph.Dependency dep = new MavenModuleDependency(nodeOf(src),nodeOf(this));
if (!dep.pointsItself())
graph.addDependency(dep);
}
}
}

/**
* Determines the source/sink of the dependency from a module.
* This is because if the build is the aggregator build, we need to define dependencies against project,
* not module.
*/
private static AbstractMavenProject<?, ?> nodeOf(MavenModule m) {
return m.getParent().isAggregatorStyleBuild() ? m.getParent() : m;
}

/**
* Returns all Maven modules in this Jenkins instance.
Expand All @@ -513,49 +525,45 @@ private boolean hasDependenciesWithUnknownVersion() {
return false;
}

private MavenModule chooseMoreRelevantModule(MavenModule mm1, MavenModule mm2, ModuleDependency moduleDependency) {
private MavenModule chooseMoreRelevantModule(MavenModule mm1, MavenModule mm2) {

if (mm1 == null) {
return mm2;
}
if (mm2 == null) {
return mm1;
}

final MavenModule moreRelevant;
final MavenModule lessRelevant;
int relevancy1 = getDependencyRelevancy(mm1);
int relevancy2 = getDependencyRelevancy(mm2);

if (relevancy1 > relevancy2) {
moreRelevant = mm1;
lessRelevant = mm2;
} else if (relevancy2 > relevancy1) {
moreRelevant = mm2;
lessRelevant = mm1;
} else {
// arbitrary, but reproduceable
if (mm1.getParent().getName().compareTo(mm2.getParent().getName()) < 0) {
moreRelevant = mm2;
lessRelevant = mm1;
} else { // should always mean > 0 as name is unique
moreRelevant = mm1;
lessRelevant = mm2;
}

int score = mm1.getDependencyRelevancy() - mm2.getDependencyRelevancy();
if (score==0) {
// tie breaker. this is arbitrary, but reproduceable
score = mm1.getParent().getFullName().compareTo(mm2.getParent().getFullName());
}

assert score!=0;

final MavenModule moreRelevant, lessRelevant;

if (score>0) { moreRelevant = mm1; lessRelevant = mm2; }
else { moreRelevant = mm2; lessRelevant = mm1; }

if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("Choosing " + moreRelevant.getParent().getName() + " over " + lessRelevant.getParent().getName()
+ " for module " + moduleDependency.getName() + ". Relevancies: " + relevancy1 + ", " + relevancy2);
+ " for module " + mm1.asDependency().getName() + ". Relevancies: " + mm1.getDependencyRelevancy() + ", " + mm2.getDependencyRelevancy());
}
return moreRelevant;
}

private int getDependencyRelevancy(MavenModule mm) {

/**
* As a guide for automatic dependency computation,
* determine how much this particular build is "relevant" to other builds on this Jenkins.
*
* If the binary is being deployed, we assume the user intends the result of this build be used elsewhere,
* so we give a higher score.
*/
private int getDependencyRelevancy() {
int relevancy = 0;

for (String goal : Util.tokenize(mm.getGoals())) {
for (String goal : Util.tokenize(getGoals())) {
if ("deploy".equals(goal) || "deploy:deploy".equals(goal)) {
return 2;
}
Expand All @@ -565,7 +573,7 @@ private int getDependencyRelevancy(MavenModule mm) {
}
}

for (Publisher publisher : mm.getParent().getPublishers()) {
for (Publisher publisher : getParent().getPublishers()) {
if (publisher instanceof RedeployPublisher) {
return 2;
}
Expand All @@ -576,14 +584,32 @@ private int getDependencyRelevancy(MavenModule mm) {

private static class MavenDependencyComputationData {
boolean withUnknownVersions = false;
Map<ModuleDependency,MavenModule> allModules;
/**
* All {@link MavenModule}s in this Jenkins, keyed by their {@link MavenModule#asDependency()}.
*/
private final Map<ModuleDependency,MavenModule> allModules;

//Map<MavenModuleSet, Map<ModuleDependency,MavenModule>> modulesPerParent = new HashMap<MavenModuleSet, Map<ModuleDependency,MavenModule>>();

public MavenDependencyComputationData(
Map<ModuleDependency, MavenModule> modules) {
public MavenDependencyComputationData(Map<ModuleDependency, MavenModule> modules) {
this.allModules = modules;
}

/**
* Builds a map of all the modules, keyed against the groupId and artifactId. The values are a list of modules
* that match this criteria.
*
* @return {@link #allModules} keyed by their {@linkplain ModuleName names}.
*/
private Multimap<ModuleName,ModuleDependency> byName() {
Multimap<ModuleName,ModuleDependency> map = HashMultimap.create();

for (ModuleDependency dependency : allModules.keySet()) {
map.put(dependency.getName(),dependency);
}

return map;
}
}

@Override
Expand Down Expand Up @@ -660,5 +686,5 @@ public String toString() {
}

private static final Logger LOGGER = Logger.getLogger(MavenModule.class.getName());

}
91 changes: 91 additions & 0 deletions src/main/java/hudson/maven/ModuleDependency.java
Expand Up @@ -23,12 +23,21 @@
*/
package hudson.maven;

import org.apache.commons.collections.comparators.ReverseComparator;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.project.MavenProject;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.model.Extension;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import hudson.Functions;

Expand All @@ -41,13 +50,30 @@
public final class ModuleDependency implements Serializable {
public final String groupId;
public final String artifactId;
/**
* Version, possibly a version range.
*/
public final String version;

/**
* @since 1.395
*/
public final boolean plugin;

/**
* Cached result of {@code VersionRange.createFromVersionSpec(version)}
*
* @see #getVersionAsRange()
*/
private VersionRange range;

/**
* Cache of the parsed form of {@link #version}
*
* @see #parseVersion()
*/
private ArtifactVersion parsedVersion;

public ModuleDependency(String groupId, String artifactId, String version) {
this(groupId, artifactId, version, false);
}
Expand Down Expand Up @@ -135,6 +161,18 @@ public int hashCode() {
return result;
}

public VersionRange getVersionAsRange() throws InvalidVersionSpecificationException {
if (range==null)
range = VersionRange.createFromVersionSpec(version);
return range;
}

public ArtifactVersion parseVersion() {
if (parsedVersion==null)
parsedVersion = new DefaultArtifactVersion(version);
return parsedVersion;
}

/**
* Upon reading from the disk, intern strings.
*/
Expand Down Expand Up @@ -163,4 +201,57 @@ protected Object readResolve() {
public static final String NONE = "-";

private static final long serialVersionUID = 1L;

/**
* Checks whether this ModuleDependency is satisfied by the dependency of the given ModuleDependency.
* This caters for versions where the version string defines a version range.
*
* @param other The dependency to check for.
* @return true if contained false otherwise.
*/
public boolean contains(ModuleDependency other) {
if (other == null || !getName().equals(other.getName()))
return false;

try {
return getVersionAsRange().containsVersion(other.parseVersion());
} catch (InvalidVersionSpecificationException ivse) {
return false;
}
}

/**
* Given a list of ModuleDependencies (of the same groupId and artifactId),
* picks the {@link ModuleDependency} that satisfies the constraint and has the highest version.
*
* @param candidates
* List that represents specific (non-range) versions.
* @return The highest satisfying ModuleDependency or null if none can be found.
*/
public ModuleDependency findHighestFrom(Collection<ModuleDependency> candidates) {
//Create a sorted map of the ModuleDependnecies sorted on version (descending order).
SortedMap<ArtifactVersion, ModuleDependency> sorted = new TreeMap<ArtifactVersion, ModuleDependency>(new ReverseComparator());
for (ModuleDependency candidate : candidates) {
sorted.put(candidate.parseVersion(), candidate);
}

//Now find the highest version that satisfies this dependency.
for (ModuleDependency e : sorted.values()) {
if (contains(e))
return e;
}

// non found
return null;
}

@Override
public String toString() {
return "ModuleDependency{" +
"groupId='" + groupId + '\'' +
", artifactId='" + artifactId + '\'' +
", version='" + version + '\'' +
", plugin=" + plugin +
'}';
}
}
2 changes: 1 addition & 1 deletion src/main/java/hudson/maven/ModuleName.java
Expand Up @@ -33,7 +33,7 @@
import java.io.Serializable;

/**
* Version independent name of a Maven project.
* Version independent name of a Maven project. GroupID+artifactId.
*
* @author Kohsuke Kawaguchi
* @see ModuleDependency
Expand Down

0 comments on commit 151a41d

Please sign in to comment.