Skip to content

Commit 4bf8444

Browse files
authored
BI-529 - Add scopes to dependencies tree nodes (#371)
1 parent f37025f commit 4bf8444

File tree

5 files changed

+135
-24
lines changed

5 files changed

+135
-24
lines changed

build-info-extractor-npm/src/main/java/org/jfrog/build/extractor/npm/NpmDriver.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.fasterxml.jackson.databind.JsonNode;
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import com.fasterxml.jackson.databind.ObjectReader;
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
67
import org.apache.commons.lang.StringUtils;
78
import org.jfrog.build.api.util.Log;
89
import org.jfrog.build.extractor.executor.CommandExecutor;
@@ -58,11 +59,16 @@ public JsonNode list(File workingDirectory, List<String> extraArgs) throws IOExc
5859
List<String> args = new ArrayList<>();
5960
args.add("ls");
6061
args.add("--json");
62+
args.add("--long");
6163
args.addAll(extraArgs);
6264
try {
6365
CommandResults npmCommandRes = commandExecutor.exeCommand(workingDirectory, args, null);
6466
String res = StringUtils.isBlank(npmCommandRes.getRes()) ? "{}" : npmCommandRes.getRes();
65-
return jsonReader.readTree(res);
67+
JsonNode npmLsResults = jsonReader.readTree(res);
68+
if (!npmCommandRes.isOk() && !npmLsResults.has("problems")) {
69+
((ObjectNode) npmLsResults).put("problems", npmCommandRes.getErr());
70+
}
71+
return npmLsResults;
6672
} catch (IOException | InterruptedException e) {
6773
throw new IOException("npm ls failed", e);
6874
}

build-info-extractor-npm/src/main/java/org/jfrog/build/extractor/npm/extractor/NpmBuildInfoExtractor.java

+16-13
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public Build extract(NpmProject npmProject) throws Exception {
6969

7070
List<Dependency> dependencies = collectDependencies(workingDir);
7171
String moduleId = StringUtils.isNotBlank(module) ? module : npmPackageInfo.toString();
72-
return createBuild(dependencies,moduleId);
72+
return createBuild(dependencies, moduleId);
7373
}
7474

7575
private void preparePrerequisites(String resolutionRepository, Path workingDir) throws IOException {
@@ -167,13 +167,13 @@ private void createTempNpmrc(Path workingDir, List<String> installationArgs) thr
167167

168168
/**
169169
* Boolean argument can be provided in one of the following ways:
170-
* 1. --arg - which infers true
171-
* 2. --arg=value (true/false)
172-
* 3. --arg value (true/false)
170+
* 1. --arg - which infers true
171+
* 2. --arg=value (true/false)
172+
* 3. --arg value (true/false)
173173
*/
174-
static boolean isJsonOutputRequired(List <String> installationArgs) {
174+
static boolean isJsonOutputRequired(List<String> installationArgs) {
175175
int jsonIndex = installationArgs.indexOf("--json");
176-
if (jsonIndex > -1 ) {
176+
if (jsonIndex > -1) {
177177
return jsonIndex == installationArgs.size() - 1 || !installationArgs.get(jsonIndex + 1).equals("false");
178178
}
179179
return installationArgs.contains("--json=true");
@@ -220,12 +220,15 @@ private void restoreNpmrc(Path workingDir) throws IOException {
220220
private List<Dependency> collectDependencies(Path workingDir) throws Exception {
221221
Map<String, Dependency> dependencies = new ConcurrentHashMap<>();
222222
List<NpmScope> scopes = getNpmScopes();
223-
for (NpmScope scope : scopes) {
224-
List<String> extraListArgs = new ArrayList<>();
225-
extraListArgs.add("--only=" + scope);
226-
JsonNode jsonNode = npmDriver.list(workingDir.toFile(), extraListArgs);
227-
populateDependenciesMap(dependencies, scope, jsonNode);
223+
if (scopes.isEmpty()) {
224+
return new ArrayList<>();
228225
}
226+
List<String> extraListArgs = new ArrayList<>();
227+
if (scopes.size() == 1) {
228+
extraListArgs.add("--only=" + scopes.get(0));
229+
}
230+
JsonNode jsonNode = npmDriver.list(workingDir.toFile(), extraListArgs);
231+
populateDependenciesMap(dependencies, jsonNode);
229232

230233
return new ArrayList<>(dependencies.values());
231234
}
@@ -268,10 +271,10 @@ private Build createBuild(List<Dependency> dependencies, String moduleId) {
268271
* 1. Create npm dependencies tree from root node of 'npm ls' command tree. Populate each node with name, version and scope.
269272
* 2. For each dependency, retrieve sha1 and md5 from Artifactory. Use the producer-consumer mechanism to parallelize it.
270273
*/
271-
private void populateDependenciesMap(Map<String, Dependency> dependencies, NpmScope scope, JsonNode npmDependenciesTree) throws Exception {
274+
private void populateDependenciesMap(Map<String, Dependency> dependencies, JsonNode npmDependenciesTree) throws Exception {
272275
// Set of packages that could not be found in Artifactory
273276
Set<NpmPackageInfo> badPackages = Collections.synchronizedSet(new HashSet<>());
274-
DefaultMutableTreeNode rootNode = NpmDependencyTree.createDependenciesTree(scope, npmDependenciesTree);
277+
DefaultMutableTreeNode rootNode = NpmDependencyTree.createDependenciesTree(npmDependenciesTree);
275278
try (ArtifactoryDependenciesClient client = dependenciesClientBuilder.build()) {
276279
// Create producer Runnable
277280
ProducerRunnableBase[] producerRunnable = new ProducerRunnableBase[]{new NpmExtractorProducer(rootNode)};
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.jfrog.build.extractor.npm.extractor;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4-
import org.apache.commons.lang3.ObjectUtils;
4+
import org.apache.commons.lang.StringUtils;
55
import org.jfrog.build.extractor.npm.types.NpmPackageInfo;
66
import org.jfrog.build.extractor.npm.types.NpmScope;
77
import org.jfrog.build.extractor.scan.DependenciesTree;
8+
import org.jfrog.build.extractor.scan.Scope;
89

10+
import java.util.HashSet;
911
import java.util.Map;
12+
import java.util.Set;
1013

1114
/**
1215
* @author Yahav Itzhak
@@ -17,18 +20,21 @@ public class NpmDependencyTree {
1720
/**
1821
* Create a npm dependencies tree from the results of 'npm ls' command.
1922
*
20-
* @param scope - 'production' or 'development'.
2123
* @param npmList - Results of 'npm ls' command.
2224
* @return Tree of npm PackageInfos.
2325
* @see NpmPackageInfo
2426
*/
25-
public static DependenciesTree createDependenciesTree(NpmScope scope, JsonNode npmList) {
27+
public static DependenciesTree createDependenciesTree(JsonNode npmList) {
2628
DependenciesTree rootNode = new DependenciesTree();
27-
populateDependenciesTree(rootNode, scope, npmList.get("dependencies"));
29+
populateDependenciesTree(rootNode, npmList.get("dependencies"));
30+
for (DependenciesTree child : rootNode.getChildren()) {
31+
NpmPackageInfo packageInfo = (NpmPackageInfo) child.getUserObject();
32+
child.setScopes(getScopes(packageInfo.getName(), packageInfo.getScope()));
33+
}
2834
return rootNode;
2935
}
3036

31-
private static void populateDependenciesTree(DependenciesTree scanTreeNode, NpmScope scope, JsonNode dependencies) {
37+
private static void populateDependenciesTree(DependenciesTree scanTreeNode, JsonNode dependencies) {
3238
if (dependencies == null) {
3339
return;
3440
}
@@ -37,16 +43,47 @@ private static void populateDependenciesTree(DependenciesTree scanTreeNode, NpmS
3743
String name = stringJsonNodeEntry.getKey();
3844
JsonNode versionNode = stringJsonNodeEntry.getValue().get("version");
3945
if (versionNode != null) {
40-
addSubtree(stringJsonNodeEntry, scanTreeNode, name, versionNode.asText(), scope); // Mutual recursive call
46+
addSubtree(stringJsonNodeEntry, scanTreeNode, name, versionNode.asText()); // Mutual recursive call
4147
}
4248
});
4349
}
4450

45-
private static void addSubtree(Map.Entry<String, JsonNode> stringJsonNodeEntry, DependenciesTree node, String name, String version, NpmScope scope) {
46-
NpmPackageInfo npmPackageInfo = new NpmPackageInfo(name, version, ObjectUtils.defaultIfNull(scope, "").toString());
47-
JsonNode childDependencies = stringJsonNodeEntry.getValue().get("dependencies");
51+
private static void addSubtree(Map.Entry<String, JsonNode> stringJsonNodeEntry, DependenciesTree node, String name, String version) {
52+
JsonNode jsonNode = stringJsonNodeEntry.getValue();
53+
String devScope = (isDev(jsonNode) ? NpmScope.DEVELOPMENT : NpmScope.PRODUCTION).toString();
54+
NpmPackageInfo npmPackageInfo = new NpmPackageInfo(name, version, devScope);
55+
JsonNode childDependencies = jsonNode.get("dependencies");
4856
DependenciesTree childTreeNode = new DependenciesTree(npmPackageInfo);
49-
populateDependenciesTree(childTreeNode, scope, childDependencies); // Mutual recursive call
57+
populateDependenciesTree(childTreeNode, childDependencies); // Mutual recursive call
5058
node.add(childTreeNode);
5159
}
60+
61+
/**
62+
* Return true if the input dependency is a dev dependency.
63+
*
64+
* @param jsonNode - The dependency node in the 'npm ls' results
65+
* @return true if the input dependency is a dev dependency
66+
*/
67+
private static boolean isDev(JsonNode jsonNode) {
68+
JsonNode development = jsonNode.get("_development");
69+
return development != null && development.asBoolean(false);
70+
}
71+
72+
/**
73+
* Return a set of the relevant scopes. The set contains 'development' or 'production'. If the dependency has a
74+
* custom scope, add it too.
75+
*
76+
* @param name - The name of the dependency
77+
* @param devScope - 'development' or 'production'
78+
* @return set of the relevant scopes
79+
*/
80+
private static Set<Scope> getScopes(String name, String devScope) {
81+
Set<Scope> scopes = new HashSet<>();
82+
scopes.add(new Scope(devScope));
83+
String customScope = StringUtils.substringBetween(name, "@", "/");
84+
if (customScope != null) {
85+
scopes.add(new Scope(customScope));
86+
}
87+
return scopes;
88+
}
5289
}

build-info-extractor/src/main/java/org/jfrog/build/extractor/scan/DependenciesTree.java

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class DependenciesTree extends DefaultMutableTreeNode {
1515

1616
private Set<Issue> issues = new HashSet<>();
1717
private Set<License> licenses = new HashSet<>();
18+
private Set<Scope> scopes = new HashSet<>();
1819
private GeneralInfo generalInfo;
1920
private Issue topIssue = new Issue();
2021

@@ -34,6 +35,10 @@ public void setLicenses(Set<License> licenses) {
3435
this.licenses = licenses;
3536
}
3637

38+
public void setScopes(Set<Scope> scopes) {
39+
this.scopes = scopes;
40+
}
41+
3742
@SuppressWarnings("unused")
3843
public void setGeneralInfo(GeneralInfo generalInfo) {
3944
this.generalInfo = generalInfo;
@@ -52,6 +57,10 @@ public Set<License> getLicenses() {
5257
return licenses;
5358
}
5459

60+
public Set<Scope> getScopes() {
61+
return scopes;
62+
}
63+
5564
/**
5665
* @return top severity issue of the current node and its ancestors
5766
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.jfrog.build.extractor.scan;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import org.apache.commons.lang.StringUtils;
5+
6+
import java.util.Objects;
7+
8+
/**
9+
* @author yahavi
10+
*/
11+
public class Scope {
12+
private static final String NONE_SCOPE = "None";
13+
14+
private final String name;
15+
16+
@SuppressWarnings("unused")
17+
public Scope() {
18+
name = NONE_SCOPE;
19+
}
20+
21+
public Scope(String name) {
22+
this.name = StringUtils.capitalize(StringUtils.trim(name));
23+
}
24+
25+
public String getName() {
26+
return name;
27+
}
28+
29+
@JsonIgnore
30+
@SuppressWarnings("unused")
31+
public boolean isEmpty() {
32+
return StringUtils.isBlank(name) || name.equals(Scope.NONE_SCOPE);
33+
}
34+
35+
@Override
36+
public boolean equals(Object other) {
37+
if (this == other) {
38+
return true;
39+
}
40+
if (other == null || getClass() != other.getClass()) {
41+
return false;
42+
}
43+
return StringUtils.equals(toString(), other.toString());
44+
}
45+
46+
@Override
47+
public int hashCode() {
48+
return Objects.hash(name);
49+
}
50+
51+
@Override
52+
public String toString() {
53+
return this.name;
54+
}
55+
56+
}

0 commit comments

Comments
 (0)