Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Merge pull request #58 from jenkinsci/JENKINS-43094-2
WIP [JENKINS-43094] Add fingerprinting of Maven Dependencies
- Loading branch information
Showing
with
701 additions
and 11 deletions.
- +33 −0 jenkins-plugin/src/main/java/org/jenkinsci/plugins/pipeline/maven/MavenSpyLogProcessor.java
- +280 −0 ...c/main/java/org/jenkinsci/plugins/pipeline/maven/publishers/DependenciesFingerprintPublisher.java
- +16 −3 jenkins-plugin/src/main/java/org/jenkinsci/plugins/pipeline/maven/util/XmlUtils.java
- +43 −0 ...ces/org/jenkinsci/plugins/pipeline/maven/publishers/DependenciesFingerprintPublisher/config.jelly
- +3 −0 ...g/jenkinsci/plugins/pipeline/maven/publishers/DependenciesFingerprintPublisher/help-disabled.html
- +3 −0 ...ources/org/jenkinsci/plugins/pipeline/maven/publishers/DependenciesFingerprintPublisher/help.html
- +122 −0 ...plugin/src/test/java/org/jenkinsci/plugins/pipeline/maven/DependencyFingerprintPublisherTest.java
- +2 −2 jenkins-plugin/src/test/java/org/jenkinsci/plugins/pipeline/maven/MavenPublisherTest.java
- +54 −0 jenkins-plugin/src/test/java/org/jenkinsci/plugins/pipeline/maven/MavenSpyLogProcessorTest.java
- +42 −0 ...st/java/org/jenkinsci/plugins/pipeline/maven/publishers/DependenciesFingerprintPublisherTest.java
- +10 −1 jenkins-plugin/src/test/resources/org/jenkinsci/plugins/pipeline/maven/maven-spy.xml
- +9 −0 ...nsci/plugins/pipeline/maven/test/test_maven_projects/mono_dependency_maven_jar_project/.gitignore
- +15 −0 ...nkinsci/plugins/pipeline/maven/test/test_maven_projects/mono_dependency_maven_jar_project/pom.xml
- +10 −0 ...ven/test/test_maven_projects/mono_dependency_maven_jar_project/src/main/java/com/example/App.java
- +59 −5 ...java/org/jenkinsci/plugins/pipeline/maven/eventspy/handler/DependencyResolutionResultHandler.java
@@ -0,0 +1,280 @@ | ||
package org.jenkinsci.plugins.pipeline.maven.publishers; | ||
|
||
import hudson.Extension; | ||
import hudson.FilePath; | ||
import hudson.model.FingerprintMap; | ||
import hudson.model.Run; | ||
import hudson.model.TaskListener; | ||
import hudson.tasks.Fingerprinter; | ||
import jenkins.model.Jenkins; | ||
import org.apache.commons.lang.StringUtils; | ||
import org.jenkinsci.Symbol; | ||
import org.jenkinsci.plugins.pipeline.maven.MavenPublisher; | ||
import org.jenkinsci.plugins.pipeline.maven.MavenSpyLogProcessor; | ||
import org.jenkinsci.plugins.pipeline.maven.util.XmlUtils; | ||
import org.jenkinsci.plugins.workflow.steps.StepContext; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
import org.w3c.dom.Element; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.TreeSet; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
import javax.annotation.Nonnull; | ||
|
||
/** | ||
* Fingerprint the dependencies of the maven project. | ||
* | ||
* @author <a href="mailto:cleclerc@cloudbees.com">Cyrille Le Clerc</a> | ||
*/ | ||
public class DependenciesFingerprintPublisher extends MavenPublisher { | ||
private static final long serialVersionUID = 1L; | ||
|
||
private static final Logger LOGGER = Logger.getLogger(MavenSpyLogProcessor.class.getName()); | ||
|
||
private boolean includeSnapshotVersions = true; | ||
|
||
private boolean includeReleaseVersions; | ||
|
||
private boolean includeScopeCompile = true; | ||
|
||
private boolean includeScopeRuntime; | ||
|
||
private boolean includeScopeTest; | ||
|
||
private boolean includeScopeProvided = true; | ||
|
||
@DataBoundConstructor | ||
public DependenciesFingerprintPublisher() { | ||
super(); | ||
} | ||
|
||
protected Set<String> getIncludedScopes() { | ||
Set<String> includedScopes = new TreeSet<>(); | ||
if (includeScopeCompile) | ||
includedScopes.add("compile"); | ||
if (includeScopeRuntime) | ||
includedScopes.add("runtime"); | ||
if (includeScopeProvided) | ||
includedScopes.add("provided"); | ||
if (includeScopeTest) | ||
includedScopes.add("test"); | ||
return includedScopes; | ||
} | ||
|
||
@Override | ||
public void process(@Nonnull StepContext context, @Nonnull Element mavenSpyLogsElt) throws IOException, InterruptedException { | ||
Run run = context.get(Run.class); | ||
TaskListener listener = context.get(TaskListener.class); | ||
|
||
FilePath workspace = context.get(FilePath.class); | ||
|
||
List<MavenSpyLogProcessor.MavenDependency> dependencies = listDependencies(mavenSpyLogsElt); | ||
|
||
if (LOGGER.isLoggable(Level.FINE)) { | ||
listener.getLogger().println("[withMaven] dependenciesFingerprintPublisher - filter: " + | ||
"versions[snapshot: " + isIncludeSnapshotVersions() + ", release: " + isIncludeReleaseVersions() + "], " + | ||
"scopes:" + getIncludedScopes()); | ||
} | ||
|
||
Map<String, String> artifactsToFingerPrint = new HashMap<>(); // artifactPathInFingerprintZone -> artifactMd5 | ||
for (MavenSpyLogProcessor.MavenDependency dependency : dependencies) { | ||
if (dependency.isSnapshot()) { | ||
if (!includeSnapshotVersions) { | ||
if (LOGGER.isLoggable(Level.FINER)) { | ||
listener.getLogger().println("[withMaven] Skip fingerprinting snapshot dependency: " + dependency); | ||
} | ||
continue; | ||
} | ||
} else { | ||
if (!includeReleaseVersions) { | ||
if (LOGGER.isLoggable(Level.FINER)) { | ||
listener.getLogger().println("[withMaven] Skip fingerprinting release dependency: " + dependency); | ||
} | ||
continue; | ||
} | ||
} | ||
if (!getIncludedScopes().contains(dependency.getScope())) { | ||
if (LOGGER.isLoggable(Level.FINER)) { | ||
listener.getLogger().println("[withMaven] Skip fingerprinting dependency with ignored scope: " + dependency); | ||
} | ||
continue; | ||
} | ||
|
||
try { | ||
if (StringUtils.isEmpty(dependency.file)) { | ||
if (LOGGER.isLoggable(Level.FINER)) { | ||
listener.getLogger().println("[withMaven] Can't fingerprint maven dependency with no file attached: " + dependency); | ||
} | ||
continue; | ||
} | ||
|
||
FilePath dependencyFilePath = new FilePath(workspace, dependency.file); | ||
|
||
if (!(dependency.file.endsWith("." + dependency.extension))) { | ||
if (dependencyFilePath.isDirectory()) { | ||
if (LOGGER.isLoggable(Level.FINE)) { | ||
listener.getLogger().println("[withMaven] Skip fingerprinting of maven dependency of type directory " + dependency); | ||
} | ||
continue; | ||
} | ||
} | ||
|
||
String dependencyMavenRepoStyleFilePath = | ||
dependency.groupId.replace('.', '/') + "/" + | ||
dependency.artifactId + "/" + | ||
dependency.version + "/" + | ||
dependency.getFileName(); | ||
|
||
|
||
if (dependencyFilePath.exists()) { | ||
// the subsequent call to digest could test the existence but we don't want to prematurely optimize performances | ||
if (LOGGER.isLoggable(Level.FINE)) { | ||
listener.getLogger().println("[withMaven] Fingerprint dependency " + dependencyMavenRepoStyleFilePath); | ||
} | ||
String artifactDigest = dependencyFilePath.digest(); | ||
artifactsToFingerPrint.put(dependencyMavenRepoStyleFilePath, artifactDigest); | ||
} else { | ||
listener.getLogger().println("[withMaven] FAILURE to fingerprint " + dependencyMavenRepoStyleFilePath + ", file not found"); | ||
} | ||
|
||
} catch (IOException | RuntimeException e) { | ||
listener.error("[withMaven] WARNING: Exception fingerprinting " + dependency + ", skip"); | ||
e.printStackTrace(listener.getLogger()); | ||
listener.getLogger().flush(); | ||
} | ||
} | ||
LOGGER.log(Level.FINER, "Fingerprint {0}", artifactsToFingerPrint); | ||
|
||
// FINGERPRINT GENERATED MAVEN ARTIFACT | ||
FingerprintMap fingerprintMap = Jenkins.getInstance().getFingerprintMap(); | ||
for (Map.Entry<String, String> artifactToFingerprint : artifactsToFingerPrint.entrySet()) { | ||
String artifactPathInFingerprintZone = artifactToFingerprint.getKey(); | ||
String artifactMd5 = artifactToFingerprint.getValue(); | ||
fingerprintMap.getOrCreate(null, artifactPathInFingerprintZone, artifactMd5).addFor(run); | ||
} | ||
|
||
// add action | ||
Fingerprinter.FingerprintAction fingerprintAction = run.getAction(Fingerprinter.FingerprintAction.class); | ||
if (fingerprintAction == null) { | ||
run.addAction(new Fingerprinter.FingerprintAction(run, artifactsToFingerPrint)); | ||
} else { | ||
fingerprintAction.add(artifactsToFingerPrint); | ||
} | ||
} | ||
|
||
/** | ||
* @param mavenSpyLogs Root XML element | ||
* @return list of {@link MavenSpyLogProcessor.MavenArtifact} | ||
*/ | ||
@Nonnull | ||
public List<MavenSpyLogProcessor.MavenDependency> listDependencies(Element mavenSpyLogs) { | ||
|
||
List<MavenSpyLogProcessor.MavenDependency> result = new ArrayList<>(); | ||
|
||
for (Element dependencyResolutionResult : XmlUtils.getChildrenElements(mavenSpyLogs, "DependencyResolutionResult")) { | ||
Element resolvedDependenciesElt = XmlUtils.getUniqueChildElementOrNull(dependencyResolutionResult, "resolvedDependencies"); | ||
|
||
if (resolvedDependenciesElt == null) { | ||
continue; | ||
} | ||
|
||
for (Element dependencyElt : XmlUtils.getChildrenElements(resolvedDependenciesElt, "dependency")) { | ||
MavenSpyLogProcessor.MavenDependency dependencyArtifact = XmlUtils.newMavenDependency(dependencyElt); | ||
|
||
Element fileElt = XmlUtils.getUniqueChildElementOrNull(dependencyElt, "file"); | ||
if (fileElt == null || fileElt.getTextContent() == null || fileElt.getTextContent().isEmpty()) { | ||
LOGGER.log(Level.WARNING, "listDependencies: no associated file found for " + dependencyArtifact + " in " + XmlUtils.toString(dependencyElt)); | ||
} else { | ||
dependencyArtifact.file = StringUtils.trim(fileElt.getTextContent()); | ||
} | ||
|
||
result.add(dependencyArtifact); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
public boolean isIncludeSnapshotVersions() { | ||
return includeSnapshotVersions; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setIncludeSnapshotVersions(boolean includeSnapshotVersions) { | ||
this.includeSnapshotVersions = includeSnapshotVersions; | ||
} | ||
|
||
public boolean isIncludeReleaseVersions() { | ||
return includeReleaseVersions; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setIncludeReleaseVersions(boolean includeReleaseVersions) { | ||
this.includeReleaseVersions = includeReleaseVersions; | ||
} | ||
|
||
public boolean isIncludeScopeCompile() { | ||
return includeScopeCompile; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setIncludeScopeCompile(boolean includeScopeCompile) { | ||
this.includeScopeCompile = includeScopeCompile; | ||
} | ||
|
||
public boolean isIncludeScopeRuntime() { | ||
return includeScopeRuntime; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setIncludeScopeRuntime(boolean includeScopeRuntime) { | ||
this.includeScopeRuntime = includeScopeRuntime; | ||
} | ||
|
||
public boolean isIncludeScopeTest() { | ||
return includeScopeTest; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setIncludeScopeTest(boolean includeScopeTest) { | ||
this.includeScopeTest = includeScopeTest; | ||
} | ||
|
||
public boolean isIncludeScopeProvided() { | ||
return includeScopeProvided; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setIncludeScopeProvided(boolean includeScopeProvided) { | ||
this.includeScopeProvided = includeScopeProvided; | ||
} | ||
|
||
@Symbol("dependenciesFingerprintPublisher") | ||
@Extension | ||
public static class DescriptorImpl extends MavenPublisher.DescriptorImpl { | ||
@Nonnull | ||
@Override | ||
public String getDisplayName() { | ||
return "Dependencies Fingerprint Publisher"; | ||
} | ||
|
||
@Override | ||
public int ordinal() { | ||
return 20; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public String getSkipFileName() { | ||
return ".skip-fingerprint-maven-dependencies"; | ||
} | ||
} | ||
} |
Oops, something went wrong.