Skip to content

Commit 40ac12f

Browse files
authored
Merge pull request #1282 from blackducksoftware/dev/devm/gradle-issue-fix
Fix IDETECT-4533: Update project name mechanism to avoid infinite loop
2 parents e2ac8e9 + 61ff040 commit 40ac12f

File tree

4 files changed

+63
-37
lines changed

4 files changed

+63
-37
lines changed

detectable/src/main/java/com/blackduck/integration/detectable/detectables/gradle/inspection/GradleInspectorExtractor.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public Extraction extract(File directory, ExecutableTarget gradleExe, @Nullable
6363
List<File> reportFiles = fileFinder.findFiles(outputDirectory,"*_dependencyGraph.txt");
6464
List<CodeLocation> codeLocations = new ArrayList<>();
6565

66+
//Get all the extraction files and sort all the files by depth number in ascending order starting from root to the deepest submodule,
67+
// this will help in getting all the rich versions for parents, and then we move on to parse child submodules to see usage of those files
6668
File[] files = new File[reportFiles.size()];
6769
reportFiles.toArray(files);
6870
List<File> reportFilesSorted = Arrays.asList(sortFilesByDepth(files));
@@ -109,6 +111,7 @@ private Optional<NameVersion> parseRootProjectMetadataFile(File rootProjectMetad
109111
}
110112
}
111113

114+
// Sort all the files containing dependency extractions in ascending-order
112115
private File[] sortFilesByDepth(File[] files) {
113116
Arrays.sort(files, new Comparator<File>() {
114117
@Override
@@ -121,7 +124,7 @@ public int compare(File o1, File o2) {
121124
private int extractDepthNumber(String name) {
122125
int i;
123126
try {
124-
int s = name.indexOf("depth") + 5;
127+
int s = name.lastIndexOf("depth") + 5; // File name is like project__projectname__depth3_dependencyGraph.txt, we extract the number after depth
125128
int e = name.indexOf("_dependencyGraph");
126129
String number = name.substring(s, e);
127130
i = Integer.parseInt(number);

detectable/src/main/java/com/blackduck/integration/detectable/detectables/gradle/inspection/parse/GradleReportLineParser.java

+48-30
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package com.blackduck.integration.detectable.detectables.gradle.inspection.parse;
22

3-
import java.util.Map;
4-
import java.util.HashMap;
53
import java.util.List;
6-
import java.util.Arrays;
74
import java.util.ArrayList;
5+
import java.util.Arrays;
6+
import java.util.Map;
7+
import java.util.HashMap;
8+
89

910
import org.apache.commons.lang3.StringUtils;
1011
import org.slf4j.Logger;
@@ -36,19 +37,19 @@ public class GradleReportLineParser {
3637
// The nested map has dependencies with their versions and each
3738
// module will have its own nested map.
3839
private final Map<String, Map<String, String>> transitiveRichVersions = new HashMap<>();
39-
40-
// This map is handling all the child-parent relationships which are found in the entire project
41-
// with each child as a key and their respective parent as the value.
42-
private final Map<String, String> relationsMap = new HashMap<>();
40+
// tracks the project where rich version was declared
4341
private String richVersionProject = null;
42+
// tracks if rich version was declared while parsing the previous line
4443
private boolean richVersionDeclared = false;
4544
private String projectName;
4645
private String rootProjectName;
47-
private String projectParent;
46+
private String projectPath;
4847
private int level;
48+
private String depthNumber;
4949
public static final String PROJECT_NAME_PREFIX = "projectName:";
5050
public static final String ROOT_PROJECT_NAME_PREFIX = "rootProjectName:";
51-
public static final String PROJECT_PARENT_PREFIX = "projectParent:";
51+
public static final String PROJECT_PATH_PREFIX = "projectPath:";
52+
public static final String FILE_NAME_PREFIX = "fileName:";
5253

5354
public GradleTreeNode parseLine(String line, Map<String, String> metadata) {
5455
level = parseTreeLevel(line);
@@ -129,15 +130,27 @@ private List<String> parseGav(String line, Map<String, String> metadata) {
129130
}
130131
}
131132

132-
projectName = metadata.getOrDefault(PROJECT_NAME_PREFIX, "orphanProject");
133-
rootProjectName = metadata.getOrDefault(ROOT_PROJECT_NAME_PREFIX, "");
134-
projectParent = metadata.getOrDefault(PROJECT_PARENT_PREFIX, "null");
133+
projectName = metadata.getOrDefault(PROJECT_NAME_PREFIX, "orphanProject"); // get project name from metadata
134+
rootProjectName = metadata.getOrDefault(ROOT_PROJECT_NAME_PREFIX, "")+"_0"; // get root project name
135+
String fileName = metadata.getOrDefault(FILE_NAME_PREFIX, "");
136+
projectPath = metadata.getOrDefault(PROJECT_PATH_PREFIX, ""); // get project path Eg: :sub:foo
137+
135138

136-
addRelation();
139+
// To avoid a bug caused by an edge case where child and parent modules have the same name causing the loop for checking rich version to stuck
140+
// in an infinite state, we are going to suffix the name of the project with the depth number
141+
int s = fileName.lastIndexOf("depth") + 5; // File name is like project__projectname__depth3_dependencyGraph.txt, we extract the number after depth
142+
int e = fileName.indexOf("_dependencyGraph");
143+
depthNumber = fileName.substring(s, e);
144+
projectName = projectName+"_"+depthNumber;
145+
146+
// Example of dependency using rich version:
147+
// --- com.graphql-java:graphql-java:{strictly [21.2, 21.3]; prefer 21.3; reject [20.6, 19.5, 18.2]} -> 21.3 direct depenendency, will be stored in rich versions, richVersionProject value will be current project
148+
// +--- com.graphql-java:java-dataloader:3.2.1 transitive needs to be stored
149+
// | \--- org.slf4j:slf4j-api:1.7.30 -> 2.0.4 transitive needs to be stored
137150

138151
if(gavPieces.size() == 3) {
139152
String dependencyGroupName = gavPieces.get(0) + ":" + gavPieces.get(1);
140-
if(level == 0 && checkRichVersionUse(cleanedOutput)) {
153+
if(level == 0 && checkRichVersionUse(cleanedOutput)) { // we only track rich versions if they are declared in direct dependencies
141154
storeDirectRichVersion(dependencyGroupName, gavPieces);
142155
} else {
143156
storeOrUpdateRichVersion(dependencyGroupName, gavPieces);
@@ -147,46 +160,51 @@ private List<String> parseGav(String line, Map<String, String> metadata) {
147160
return gavPieces;
148161
}
149162

163+
// store the dependency where rich version was declared and update the global tracking values
150164
private void storeDirectRichVersion(String dependencyGroupName, List<String> gavPieces) {
151165
gradleRichVersions.computeIfAbsent(projectName, value -> new HashMap<>()).putIfAbsent(dependencyGroupName, gavPieces.get(2));
152166
richVersionProject = projectName;
153167
richVersionDeclared = true;
154168
}
155169

156170
private void storeOrUpdateRichVersion(String dependencyGroupName, List<String> gavPieces) {
171+
// this condition is checking for rich version use for current direct dependency in one of the parent submodule of the current module and updates the current version
157172
if (checkParentRichVersion(dependencyGroupName)) {
158173
gavPieces.set(2, gradleRichVersions.get(richVersionProject).get(dependencyGroupName));
159174
} else if(checkIfTransitiveRichVersion() && transitiveRichVersions.containsKey(richVersionProject) && transitiveRichVersions.get(richVersionProject).containsKey(dependencyGroupName)) {
175+
// this is checking if we are parsing a transitive dependency and that transitive
176+
// dependency has already been memoized for the use of rich version
160177
gavPieces.set(2, transitiveRichVersions.get(richVersionProject).get(dependencyGroupName));
161178
} else if (checkIfTransitiveRichVersion() && richVersionDeclared) {
179+
// if while parsing the last direct dependency, we found the use of rich version, we store the version resolved for this transitive dependency
162180
transitiveRichVersions.computeIfAbsent(richVersionProject, value -> new HashMap<>()).putIfAbsent(dependencyGroupName, gavPieces.get(2));
163181
} else {
182+
// no use of rich versions found
164183
richVersionDeclared = false;
165184
richVersionProject = null;
166185
}
167186
}
168187

169-
private void addRelation() {
170-
if (!projectParent.equals("null") && !projectParent.contains("root project")) {
171-
String parentString = projectParent.substring(projectParent.lastIndexOf(":") + 1, projectParent.lastIndexOf("'"));
172-
relationsMap.putIfAbsent(projectName, parentString);
173-
} else if (!projectParent.equals("null") && !projectName.equals(rootProjectName)) {
174-
relationsMap.putIfAbsent(projectName, rootProjectName);
175-
} else {
176-
relationsMap.putIfAbsent(rootProjectName, null);
188+
private boolean checkParentRichVersion(String dependencyGroupName) {
189+
// this method checks all the parent modules for the current submodule upto rootProject for the use of the rich version for the current dependency
190+
// if the rich version is used return true and update the richVersionProject
191+
// We will check if rich version was declared in root project, if yes immediately apply it, otherwise parse the whole project path for the current submodule
192+
// path will start from level 1 Eg: :sub:foo, we will check dependency in :sub_1 first foo_2 next where the name is similar to project name we put in the gradle Rich versions map.
193+
//Eg: if sub declares rich version and foo is child of both sub and subtwo, we change version if :sub:foo is the path we are parsing and do not change if we are parsing :subtwo:foo
194+
195+
if(gradleRichVersions.containsKey(rootProjectName) && gradleRichVersions.get(rootProjectName).containsKey(dependencyGroupName)) {
196+
richVersionProject = rootProjectName;
197+
return true;
177198
}
178-
}
179199

180-
private boolean checkParentRichVersion(String dependencyGroupName) {
181-
String currentProject = projectName;
182-
while (currentProject != null) {
183-
if (gradleRichVersions.containsKey(currentProject) && gradleRichVersions.get(currentProject).containsKey(dependencyGroupName)) {
184-
richVersionProject = currentProject;
200+
String[] pathParts = projectPath.split(":");
201+
for(int depth = 1; depth < pathParts.length; depth++) { // Since path is like :sub:foo we start at the first index which will be the parent at first level
202+
if(gradleRichVersions.containsKey(pathParts[depth]+"_"+depth) && gradleRichVersions.get(pathParts[depth]+"_"+depth).containsKey(dependencyGroupName)) {
203+
richVersionProject = pathParts[depth]+"_"+depth;
185204
return true;
186205
}
187-
currentProject = relationsMap.getOrDefault(currentProject, null);
188206
}
189-
return false;
207+
return false;
190208
}
191209

192210
private boolean checkRichVersionUse(String dependencyLine) {

detectable/src/main/java/com/blackduck/integration/detectable/detectables/gradle/inspection/parse/GradleReportParser.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public class GradleReportParser {
2626
public static final String PROJECT_DIRECTORY_PREFIX = "projectDirectory:";
2727
public static final String PROJECT_GROUP_PREFIX = "projectGroup:";
2828
public static final String PROJECT_NAME_PREFIX = "projectName:";
29-
public static final String PROJECT_PARENT_PREFIX = "projectParent:";
3029
public static final String PROJECT_VERSION_PREFIX = "projectVersion:";
30+
public static final String PROJECT_PATH_PREFIX = "projectPath:";
3131
public static final String ROOT_PROJECT_NAME_PREFIX = "rootProjectName:";
3232
public static final String ROOT_PROJECT_VERSION_PREFIX = "rootProjectVersion:";
3333
public static final String DETECT_META_DATA_HEADER = "DETECT META DATA START";
3434
public static final String DETECT_META_DATA_FOOTER = "DETECT META DATA END";
35-
private final Map<String, String> metadata = new HashMap<>();
35+
private final Map<String, String> metadata = new HashMap<>(); // important metadata to pass to line parsers for rich versions
3636
private final GradleReportConfigurationParser gradleReportConfigurationParser = new GradleReportConfigurationParser();
3737

3838
public Optional<GradleReport> parseReport(File reportFile) {
@@ -44,10 +44,14 @@ public Optional<GradleReport> parseReport(File reportFile) {
4444

4545
List<String> reportLines = reader.lines().collect(Collectors.toList());
4646

47+
// we parse the last few lines of the extraction file, as we have the metadata information at the end of the file
48+
// such as project name, project parent etc. This information is helpful in getting the rich version information by storing parent information
49+
// and then parse childs to see if the rich versions declared are used.
4750
for(int i = reportLines.size()-1; i>=0; i--) {
4851
if (reportLines.get(i).startsWith(DETECT_META_DATA_FOOTER)) {
4952
processingMetaData = true;
5053
} else if (reportLines.get(i).startsWith(DETECT_META_DATA_HEADER)) {
54+
metadata.put("fileName:", reportFile.getName());
5155
break;
5256
} else if (processingMetaData) {
5357
setGradleReportInfo(gradleReport, reportLines.get(i));
@@ -80,17 +84,17 @@ private void setGradleReportInfo(GradleReport gradleReport, String line) {
8084
} else if (line.startsWith(PROJECT_GROUP_PREFIX)) {
8185
gradleReport.setProjectGroup(line.substring(PROJECT_GROUP_PREFIX.length()).trim());
8286
} else if (line.startsWith(PROJECT_NAME_PREFIX)) {
83-
String projectName = line.substring(PROJECT_NAME_PREFIX.length()).trim();
87+
String projectName = line.substring(PROJECT_NAME_PREFIX.length()).trim(); // get project name
8488
gradleReport.setProjectName(projectName);
8589
metadata.put(PROJECT_NAME_PREFIX, projectName);
8690
} else if (line.startsWith(PROJECT_VERSION_PREFIX)) {
8791
gradleReport.setProjectVersionName(line.substring(PROJECT_VERSION_PREFIX.length()).trim());
8892
} else if (line.startsWith(ROOT_PROJECT_NAME_PREFIX)) {
8993
String rootProjectName = line.substring(ROOT_PROJECT_NAME_PREFIX.length()).trim();
9094
metadata.put(ROOT_PROJECT_NAME_PREFIX, rootProjectName);
91-
} else if (line.startsWith(PROJECT_PARENT_PREFIX)) {
92-
String projectParent = line.substring(PROJECT_PARENT_PREFIX.length()).trim();
93-
metadata.put(PROJECT_PARENT_PREFIX, projectParent);
95+
} else if (line.startsWith(PROJECT_PATH_PREFIX)) {
96+
String projectParent = line.substring(PROJECT_PATH_PREFIX.length()).trim(); // get current project's path Eg: :subtwo:foo
97+
metadata.put(PROJECT_PATH_PREFIX, projectParent);
9498
}
9599
}
96100

documentation/src/main/markdown/currentreleasenotes.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
### Resolved issues
2424

2525
* (IDETECT-4447) - ID strings of detected Yarn project dependencies are now correctly formed. Related warning messages have been improved to identify entries in the yarn.lock file that have not been resolved through package.json files and could not be resolved with any standard NPM packages.
26+
* (IDETECT-4533) - Resolved an issue with [detect_product_short] Gradle Native Inspector causing scans to hang indefinitely when submodule has the same name as the parent module.
2627

2728
### Dependency updates
2829

0 commit comments

Comments
 (0)