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

Add requestBy info to dependency node #82

Open
wants to merge 1 commit 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
6 changes: 5 additions & 1 deletion src/main/java/org/jfrog/buildinfo/ArtifactoryMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectDependenciesResolver;
import org.apache.maven.settings.Proxy;
import org.jfrog.build.extractor.ci.BuildInfoFields;
import org.jfrog.build.extractor.clientConfiguration.ArtifactoryClientConfiguration;
Expand Down Expand Up @@ -47,6 +48,9 @@ public class ArtifactoryMojo extends AbstractMojo {
@Component(role = RepositoryListener.class)
RepositoryListener repositoryListener;

@Component(role = ProjectDependenciesResolver.class)
ProjectDependenciesResolver dependenciesResolver;

@Parameter
Config.Artifactory artifactory = new Config.Artifactory();

Expand Down Expand Up @@ -125,7 +129,7 @@ private void enforceDeployment() {
skipDefaultDeploy();
completeConfig();
addDeployProperties();
BuildInfoRecorder executionListener = new BuildInfoRecorder(session, getLog(), artifactory.delegate);
BuildInfoRecorder executionListener = new BuildInfoRecorder(session, getLog(), artifactory.delegate, dependenciesResolver);
repositoryListener.setBuildInfoRecorder(executionListener);
session.getRequest().setExecutionListener(executionListener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.DefaultDependencyResolutionRequest;
import org.apache.maven.project.DependencyResolutionResult;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectDependenciesResolver;
import org.eclipse.aether.graph.DependencyNode;
import org.jfrog.build.extractor.BuildInfoExtractor;
import org.jfrog.build.extractor.BuildInfoExtractorUtils;
import org.jfrog.build.extractor.builder.ArtifactBuilder;
Expand All @@ -28,9 +32,12 @@
import org.jfrog.build.extractor.packageManager.PackageManagerUtils;
import org.jfrog.buildinfo.resolution.RepositoryListener;
import org.jfrog.buildinfo.types.ModuleArtifacts;
import org.jfrog.buildinfo.utils.DependencyResolutionUtil;
import org.jfrog.buildinfo.utils.ReactorDependencyFilter;
import org.jfrog.buildinfo.utils.Utils;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
Expand Down Expand Up @@ -61,6 +68,8 @@ public class BuildInfoRecorder implements BuildInfoExtractor<ExecutionEvent>, Ex
private final ExecutionListener wrappedListener;
private final BuildDeployer buildDeployer;
private final Log logger;
private Map<String, String[][]> dependencyParentsMap;
private ProjectDependenciesResolver dependenciesResolver;

public BuildInfoRecorder(MavenSession session, Log logger, ArtifactoryClientConfiguration conf) {
this.wrappedListener = ObjectUtils.defaultIfNull(session.getRequest().getExecutionListener(), new AbstractExecutionListener());
Expand All @@ -70,6 +79,15 @@ public BuildInfoRecorder(MavenSession session, Log logger, ArtifactoryClientConf
this.conf = conf;
}

public BuildInfoRecorder(MavenSession session, Log logger, ArtifactoryClientConfiguration conf, ProjectDependenciesResolver dependenciesResolver) {
this.wrappedListener = ObjectUtils.defaultIfNull(session.getRequest().getExecutionListener(), new AbstractExecutionListener());
this.buildInfoBuilder = new BuildInfoModelPropertyResolver(logger, session, conf);
this.buildDeployer = new BuildDeployer(logger);
this.logger = logger;
this.conf = conf;
this.dependenciesResolver = dependenciesResolver;
}

/**
* Collect artifacts and dependencies from the project.
*
Expand All @@ -94,6 +112,11 @@ public void projectSucceeded(ExecutionEvent event) {
addArtifactsToCurrentModule(project, moduleBuilder);
}

// Construct map of dependency to parents
if (dependenciesResolver != null) {
DependencyNode dependencyNode = getDependencyNode(project, event.getSession());
dependencyParentsMap = DependencyResolutionUtil.createDependencyParentsMap(dependencyNode);
}
// Fill currentModuleDependencies
addDependencies(project);

Expand All @@ -110,6 +133,28 @@ public void projectSucceeded(ExecutionEvent event) {
wrappedListener.projectSucceeded(event);
}

/**
* Get current project DependencyNode
* @param project The Maven project
* @param session The Maven session
* @return The DependencyNode Object
*/
private DependencyNode getDependencyNode(MavenProject project, MavenSession session) {
Collection<Artifact> projectArtifacts = new HashSet<>();
projectArtifacts.add(project.getArtifact());
DefaultDependencyResolutionRequest request =
new DefaultDependencyResolutionRequest(project, session.getRepositorySession());
// Filter current project artifact
request.setResolutionFilter(new ReactorDependencyFilter(projectArtifacts));
try {
DependencyResolutionResult result = dependenciesResolver.resolve(request);
return result.getDependencyGraph();
} catch (Exception e) {
logger.error(e);
}
return null;
}

/**
* Add dependencies to the current running project.
*
Expand Down Expand Up @@ -289,9 +334,13 @@ private void addArtifactsToCurrentModule(MavenProject project, ModuleBuilder mod
private void addDependenciesToCurrentModule(ModuleBuilder moduleBuilder) {
for (Artifact moduleDependency : currentModuleDependencies.getOrCreate()) {
File depFile = moduleDependency.getFile();
String gav = getModuleIdString(moduleDependency.getGroupId(), moduleDependency.getArtifactId(), moduleDependency.getVersion());
DependencyBuilder dependencyBuilder = new DependencyBuilder()
.id(getModuleIdString(moduleDependency.getGroupId(), moduleDependency.getArtifactId(), moduleDependency.getVersion()))
.type(getTypeString(moduleDependency.getType(), moduleDependency.getClassifier(), getFileExtension(depFile)));
.id(gav).type(getTypeString(moduleDependency.getType(), moduleDependency.getClassifier(), getFileExtension(depFile)));
// add RequestBy to Dependency
if (dependenciesResolver != null) {
dependencyBuilder.requestedBy(dependencyParentsMap.get(gav));
}
String scopes = moduleDependency.getScope();
if (StringUtils.isNotBlank(scopes)) {
dependencyBuilder.scopes(Sets.newHashSet(scopes));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.jfrog.buildinfo.utils;

import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.DependencyNode;
import org.jfrog.build.extractor.BuildInfoExtractorUtils;

import java.util.*;

public class DependencyResolutionUtil {

/**
* Create a map of dependency to parents.
* Key - dependency ID - group:artifact:version.
* Value - parents path-to-module. For example:
* [["parentIdA", "a1", "a2",... "moduleId"]
* ["parentIdB", "b1", "b2",... "moduleId"]]
*
* @param dependencyNode - The root dependency node
* @return map of dependency to parents.
* @see org.jfrog.build.api.Dependency#setRequestedBy(String[][])
*/
public static Map<String, String[][]> createDependencyParentsMap(DependencyNode dependencyNode) {
if (dependencyNode == null) {
return Collections.EMPTY_MAP;
}
Map<String, String[][]> dependencyParentsMap = new HashMap<>();

for (DependencyNode child : dependencyNode.getChildren()) {
String childGav = getGavString(child);
// Populate the direct children with the module's GAV
List<String> parents = Collections.singletonList(getGavString(dependencyNode));
addParent(dependencyParentsMap, childGav, parents);

// Create dependency parent map for children
createDependencyParentsMap(dependencyParentsMap, child, parents);
}
return dependencyParentsMap;
}

/**
* Recursively create a requirements map for transitive dependencies.
*
* @param dependencyParentsMap - Output - The map to populate
* @param dependencyNode - The current dependency node
* @param parent - The parent path-to-module list
*/
private static void createDependencyParentsMap(Map<String, String[][]> dependencyParentsMap, DependencyNode dependencyNode, List<String> parent) {
List<DependencyNode> children = dependencyNode.getChildren();
if (children == null || children.isEmpty()) {
return;
}

// Create the parent path-to-module for the children
List<String> childParents = new ArrayList<>(parent);
childParents.add(0, getGavString(dependencyNode));

for (DependencyNode child : dependencyNode.getChildren()) {
String childGav = getGavString(child);
addParent(dependencyParentsMap, childGav, childParents);
createDependencyParentsMap(dependencyParentsMap, child, childParents);
}
}

/**
* Add parent to the dependency.
*
* @param dependencyParentsMap - The dependency parents map
* @param childGav - The child dependency GAV
* @param parent - The parent path-to-module list to add to the map
*/
private static void addParent(Map<String, String[][]> dependencyParentsMap, String childGav, List<String> parent) {
// Get current parents
String[][] currentParents = dependencyParentsMap.getOrDefault(childGav, new String[][]{});

// Add the input parent to the current parents
currentParents = (String[][]) ArrayUtils.add(currentParents, parent.toArray(ArrayUtils.EMPTY_STRING_ARRAY));

// Set the updated parents
dependencyParentsMap.put(childGav, currentParents);
}

private static String getGavString(DependencyNode dependencyNode) {
Artifact artifact = dependencyNode.getArtifact();
return BuildInfoExtractorUtils.getModuleIdString(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.jfrog.buildinfo.utils;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* A filter to exclude dependency nodes by artifacts.
*
*/
public class ReactorDependencyFilter implements DependencyFilter {

private Set<String> keys = new HashSet<>();

public ReactorDependencyFilter(Collection<Artifact> artifacts) {
for (Artifact artifact : artifacts) {
String key = ArtifactUtils.key(artifact);
keys.add(key);
}
}

/**
* Indicates whether the specified dependency node shall be included or excluded.
*
* @param node The dependency node to filter, must not be {@code null}.
* @param parents The (read-only) chain of parent nodes that leads to the node to be filtered, must not be
* {@code null}. Iterating this (possibly empty) list walks up the dependency graph towards the root
* node, i.e. the immediate parent node (if any) is the first node in the list. The size of the list also
* denotes the zero-based depth of the filtered node.
* @return {@code true} to include the dependency node, {@code false} to exclude it.
*/
@Override
public boolean accept(DependencyNode node, List<DependencyNode> parents) {
Dependency dependency = node.getDependency();
if (dependency != null) {
org.eclipse.aether.artifact.Artifact artifact = dependency.getArtifact();
String key = ArtifactUtils.key(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion());
return !keys.contains(key);
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectDependenciesResolver;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.DefaultRepositorySystemSession;
Expand Down Expand Up @@ -45,6 +47,9 @@ public abstract class ArtifactoryMojoTestBase extends AbstractMojoTestCase {

static Date TEST_DATE = createTestDate();

@Component(role = ProjectDependenciesResolver.class)
ProjectDependenciesResolver dependenciesResolver;

@Before
public void setUp() throws Exception {
super.setUp();
Expand Down Expand Up @@ -141,5 +146,7 @@ private void fillMojoFromConfiguration(Xpp3Dom configuration) throws JsonProcess
Log log = new MavenLogger();
mojo.setLog(log);
mojo.repositoryListener = new RepositoryListener(new PlexusLogger(log));
mojo.dependenciesResolver = dependenciesResolver;

}
}
4 changes: 3 additions & 1 deletion src/test/java/org/jfrog/buildinfo/BuildInfoRecorderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class BuildInfoRecorderTest extends ArtifactoryMojoTestBase {
public void setUp() throws Exception {
super.setUp();
executionEvent = new TestExecutionEvent(mojo.session, mojo.project);
buildInfoRecorder = new BuildInfoRecorder(mojo.session, mojo.getLog(), mojo.artifactory.delegate);
buildInfoRecorder = new BuildInfoRecorder(mojo.session, mojo.getLog(), mojo.artifactory.delegate, mojo.dependenciesResolver);
mojo.project.setArtifacts(Sets.newHashSet(TEST_ARTIFACT));
}

Expand Down Expand Up @@ -78,6 +78,8 @@ public void testProjectSucceeded() {
assertEquals(1, dependency.getScopes().size());
assertTrue(dependency.getScopes().contains("compile"));
assertEquals(TEST_ARTIFACT.getType(), dependency.getType());
assertEquals(null, dependency.getRequestedBy());

}

public void testExtract() {
Expand Down