Skip to content

Commit 6747d65

Browse files
authored
LinkageProblemCauseAnnotator to take ClassPathBuilder to configure repositories (GoogleCloudPlatform#1642)
* Annotator to take ClassPathBuilder to configure repositories * Not relying on non-null * Split the case of JAR files and artifacts
1 parent 343c069 commit 6747d65

11 files changed

Lines changed: 197 additions & 118 deletions

File tree

‎boms/integration-tests/src/test/java/com/google/cloud/MaximumLinkageErrorsTest.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void testForNewLinkageErrors()
6060
StringBuilder message = new StringBuilder("Baseline BOM: " + baselineCoordinates + "\n");
6161
if (!newProblems.isEmpty()) {
6262
message.append("Newly introduced problems:\n");
63-
message.append(LinkageProblem.formatLinkageProblems(newProblems));
63+
message.append(LinkageProblem.formatLinkageProblems(newProblems, null));
6464
Assert.fail(message.toString());
6565
}
6666
}

‎dependencies/src/main/java/com/google/cloud/tools/opensource/classpath/LinkageCheckerMain.java‎

Lines changed: 88 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import com.google.common.collect.ImmutableSet;
2525
import java.io.IOException;
2626
import java.nio.file.Path;
27-
import java.util.ArrayList;
28-
import java.util.List;
2927
import javax.xml.stream.XMLStreamException;
3028
import javax.xml.transform.TransformerException;
3129
import org.apache.commons.cli.ParseException;
@@ -57,85 +55,19 @@ public static void main(String[] arguments)
5755
linkageCheckerArguments.printHelp();
5856
}
5957

60-
if (linkageCheckerArguments.hasInput()) {
61-
// This is non-empty if a BOM or artifacts are specified in the argument
62-
ImmutableList<Artifact> artifacts = linkageCheckerArguments.getArtifacts();
63-
64-
// When JAR files are specified in the argument, artifacts are empty.
65-
ImmutableList<ClassPathEntry> inputClassPath;
66-
ImmutableSet<ClassPathEntry> entryPoints;
67-
List<ArtifactProblem> artifactProblems = new ArrayList<>();
68-
// classPathResult is kept null if JAR files are specified in the argument
69-
ClassPathResult classPathResult = null;
70-
71-
if (artifacts.isEmpty()) {
72-
// When JAR files are passed as arguments, classPathResult is null, because there is no need
73-
// to resolve Maven dependencies.
74-
inputClassPath = linkageCheckerArguments.getJarFiles();
75-
entryPoints = ImmutableSet.copyOf(inputClassPath);
76-
} else {
77-
// When a BOM or Maven artifacts are passed as arguments, resolve the dependencies.
78-
DependencyGraphBuilder dependencyGraphBuilder =
79-
new DependencyGraphBuilder(linkageCheckerArguments.getMavenRepositoryUrls());
80-
ClassPathBuilder classPathBuilder = new ClassPathBuilder(dependencyGraphBuilder);
81-
classPathResult = classPathBuilder.resolve(artifacts, false);
82-
inputClassPath = classPathResult.getClassPath();
83-
artifactProblems.addAll(classPathResult.getArtifactProblems());
84-
entryPoints = ImmutableSet.copyOf(inputClassPath.subList(0, artifacts.size()));
85-
}
86-
87-
LinkageChecker linkageChecker =
88-
LinkageChecker.create(
89-
inputClassPath, entryPoints, linkageCheckerArguments.getInputExclusionFile());
90-
ImmutableSet<LinkageProblem> linkageProblems = linkageChecker.findLinkageProblems();
91-
92-
if (linkageCheckerArguments.getReportOnlyReachable()) {
93-
ClassReferenceGraph graph = linkageChecker.getClassReferenceGraph();
94-
linkageProblems =
95-
linkageProblems.stream()
96-
.filter(
97-
(LinkageProblem problem) ->
98-
graph.isReachable(problem.getSourceClass().getBinaryName()))
99-
.collect(toImmutableSet());
100-
}
101-
102-
if (classPathResult != null) {
103-
LinkageProblemCauseAnnotator.annotate(classPathResult, linkageProblems);
104-
}
105-
106-
Path writeAsExclusionFile = linkageCheckerArguments.getOutputExclusionFile();
107-
if (writeAsExclusionFile != null) {
108-
ExclusionFiles.write(writeAsExclusionFile, linkageProblems);
109-
System.out.println("Wrote the linkage errors as exclusion file: " + writeAsExclusionFile);
110-
return;
111-
}
58+
if (linkageCheckerArguments.hasInput()) {
59+
// artifactsInArguments is not empty if a BOM or artifacts are specified in the argument.
60+
// If JAR files are specified, it's empty.
61+
ImmutableList<Artifact> artifactsInArguments = linkageCheckerArguments.getArtifacts();
11262

63+
ImmutableSet<LinkageProblem> linkageProblems =
64+
artifactsInArguments.isEmpty()
65+
? checkJarFiles(linkageCheckerArguments)
66+
: checkArtifacts(linkageCheckerArguments);
11367
if (!linkageProblems.isEmpty()) {
114-
System.out.println(LinkageProblem.formatLinkageProblems(linkageProblems));
115-
}
116-
117-
if (classPathResult != null && !linkageProblems.isEmpty()) {
118-
ImmutableSet.Builder<ClassPathEntry> problematicJars = ImmutableSet.builder();
119-
for (LinkageProblem linkageProblem : linkageProblems) {
120-
ClassFile targetClass = linkageProblem.getTargetClass();
121-
if (targetClass != null) {
122-
problematicJars.add(targetClass.getClassPathEntry());
123-
}
124-
ClassFile sourceClassFile = linkageProblem.getSourceClass();
125-
problematicJars.add(sourceClassFile.getClassPathEntry());
126-
}
127-
System.out.println(classPathResult.formatDependencyPaths(problematicJars.build()));
128-
}
129-
130-
if (!artifactProblems.isEmpty()) {
131-
System.out.println("\n");
132-
System.out.println(ArtifactProblem.formatProblems(artifactProblems));
13368
System.out.println(
13469
"For the details of the linkage errors, see "
13570
+ "https://github.com/GoogleCloudPlatform/cloud-opensource-java/wiki/Linkage-Checker-Messages");
136-
}
137-
138-
if (!linkageProblems.isEmpty()) {
13971
// Throwing an exception is more test-friendly compared with System.exit(1). The latter
14072
// abruptly stops test execution.
14173
throw new LinkageCheckResultException(linkageProblems.size());
@@ -145,4 +77,84 @@ public static void main(String[] arguments)
14577
System.err.println(ex.getMessage());
14678
}
14779
}
80+
81+
private static ImmutableSet<LinkageProblem> checkJarFiles(
82+
LinkageCheckerArguments linkageCheckerArguments)
83+
throws IOException, TransformerException, XMLStreamException {
84+
ImmutableList<ClassPathEntry> inputClassPath = linkageCheckerArguments.getJarFiles();
85+
ImmutableSet<ClassPathEntry> entryPoints = ImmutableSet.copyOf(inputClassPath);
86+
LinkageChecker linkageChecker =
87+
LinkageChecker.create(
88+
inputClassPath, entryPoints, linkageCheckerArguments.getInputExclusionFile());
89+
90+
ImmutableSet<LinkageProblem> linkageProblems =
91+
findLinkageProblems(linkageCheckerArguments, linkageChecker);
92+
93+
if (!linkageProblems.isEmpty()) {
94+
System.out.println(LinkageProblem.formatLinkageProblems(linkageProblems, null));
95+
}
96+
97+
return linkageProblems;
98+
}
99+
100+
private static ImmutableSet<LinkageProblem> checkArtifacts(
101+
LinkageCheckerArguments linkageCheckerArguments)
102+
throws IOException, RepositoryException, TransformerException, XMLStreamException {
103+
ImmutableList<Artifact> artifactsInArguments = linkageCheckerArguments.getArtifacts();
104+
105+
// When a BOM or Maven artifacts are passed as arguments, resolve the dependencies.
106+
DependencyGraphBuilder dependencyGraphBuilder =
107+
new DependencyGraphBuilder(linkageCheckerArguments.getMavenRepositoryUrls());
108+
ClassPathBuilder classPathBuilder = new ClassPathBuilder(dependencyGraphBuilder);
109+
ClassPathResult classPathResult = classPathBuilder.resolve(artifactsInArguments, false);
110+
ImmutableList<ClassPathEntry> inputClassPath = classPathResult.getClassPath();
111+
ImmutableList<ArtifactProblem> artifactProblems =
112+
ImmutableList.copyOf(classPathResult.getArtifactProblems());
113+
ImmutableSet<ClassPathEntry> entryPoints =
114+
ImmutableSet.copyOf(inputClassPath.subList(0, artifactsInArguments.size()));
115+
116+
LinkageChecker linkageChecker =
117+
LinkageChecker.create(
118+
inputClassPath, entryPoints, linkageCheckerArguments.getInputExclusionFile());
119+
ImmutableSet<LinkageProblem> linkageProblems =
120+
findLinkageProblems(linkageCheckerArguments, linkageChecker);
121+
122+
LinkageProblemCauseAnnotator.annotate(classPathBuilder, classPathResult, linkageProblems);
123+
124+
if (!linkageProblems.isEmpty()) {
125+
System.out.println(LinkageProblem.formatLinkageProblems(linkageProblems, classPathResult));
126+
}
127+
128+
if (!artifactProblems.isEmpty()) {
129+
System.out.println("\n");
130+
System.out.println(ArtifactProblem.formatProblems(artifactProblems));
131+
}
132+
return linkageProblems;
133+
}
134+
135+
private static ImmutableSet<LinkageProblem> findLinkageProblems(
136+
LinkageCheckerArguments linkageCheckerArguments, LinkageChecker linkageChecker)
137+
throws IOException, TransformerException, XMLStreamException {
138+
139+
ImmutableSet<LinkageProblem> linkageProblems = linkageChecker.findLinkageProblems();
140+
141+
if (linkageCheckerArguments.getReportOnlyReachable()) {
142+
ClassReferenceGraph graph = linkageChecker.getClassReferenceGraph();
143+
linkageProblems =
144+
linkageProblems.stream()
145+
.filter(
146+
(LinkageProblem problem) ->
147+
graph.isReachable(problem.getSourceClass().getBinaryName()))
148+
.collect(toImmutableSet());
149+
}
150+
151+
Path outputExclusionFile = linkageCheckerArguments.getOutputExclusionFile();
152+
if (outputExclusionFile != null) {
153+
ExclusionFiles.write(outputExclusionFile, linkageProblems);
154+
System.out.println("Wrote the linkage errors as exclusion file: " + outputExclusionFile);
155+
return ImmutableSet.of();
156+
}
157+
158+
return linkageProblems;
159+
}
148160
}

‎dependencies/src/main/java/com/google/cloud/tools/opensource/classpath/LinkageProblem.java‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,13 @@ public static ImmutableMap<String, ImmutableSet<String>> groupBySymbolProblem(
152152
return ImmutableMap.copyOf(valueTransformed);
153153
}
154154

155-
/** Returns the formatted {@code linkageProblems} by grouping them by the {@code symbol}s. */
156-
public static String formatLinkageProblems(Set<LinkageProblem> linkageProblems) {
155+
/**
156+
* Returns the formatted {@code linkageProblems} by grouping them by the {@code symbol}s. If
157+
* {@code classPathResult} is not null, it supplies dependency paths from the root to the
158+
* artifacts in the problems.
159+
*/
160+
public static String formatLinkageProblems(
161+
Set<LinkageProblem> linkageProblems, @Nullable ClassPathResult classPathResult) {
157162
StringBuilder output = new StringBuilder();
158163

159164
// Don't group AbstractMethodProblems by symbols because they do not fit in the
@@ -215,9 +220,30 @@ public static String formatLinkageProblems(Set<LinkageProblem> linkageProblems)
215220
output.append(" " + causeWithIndent + "\n");
216221
}
217222

223+
if (classPathResult != null) {
224+
String dependencyPaths = dependencyPathsOfProblematicJars(classPathResult, linkageProblems);
225+
output.append(dependencyPaths);
226+
}
227+
218228
return output.toString();
219229
}
220230

231+
private static String dependencyPathsOfProblematicJars(
232+
ClassPathResult classPathResult, Set<LinkageProblem> linkageProblems) {
233+
ImmutableSet.Builder<ClassPathEntry> problematicJars = ImmutableSet.builder();
234+
for (LinkageProblem problem : linkageProblems) {
235+
if (problem.getTargetClass() != null) {
236+
problematicJars.add(problem.getTargetClass().getClassPathEntry());
237+
}
238+
239+
ClassFile sourceClass = problem.getSourceClass();
240+
problematicJars.add(sourceClass.getClassPathEntry());
241+
}
242+
243+
return "Problematic artifacts in the dependency tree:\n"
244+
+ classPathResult.formatDependencyPaths(problematicJars.build());
245+
}
246+
221247
String describe(DependencyConflict conflict) {
222248
DependencyPath pathToSelectedArtifact = conflict.getPathToSelectedArtifact();
223249
Artifact selected = pathToSelectedArtifact.getLeaf();

‎dependencies/src/main/java/com/google/cloud/tools/opensource/classpath/LinkageProblemCauseAnnotator.java‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.google.cloud.tools.opensource.classpath;
1818

19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
1921
import com.google.cloud.tools.opensource.dependencies.DependencyPath;
2022
import java.io.IOException;
2123
import java.util.HashMap;
@@ -30,12 +32,19 @@ private LinkageProblemCauseAnnotator() {}
3032
/**
3133
* Annotates the cause field of {@link LinkageProblem}s with the {@link LinkageProblemCause}.
3234
*
35+
* @param classPathBuilder class path builder to resolve dependency graphs
3336
* @param rootResult the class path used for generating the linkage problems
3437
* @param linkageProblems linkage problems to annotate
3538
* @throws IOException when there is a problem reading JAR files
3639
*/
37-
public static void annotate(ClassPathResult rootResult, Iterable<LinkageProblem> linkageProblems)
40+
public static void annotate(
41+
ClassPathBuilder classPathBuilder,
42+
ClassPathResult rootResult,
43+
Iterable<LinkageProblem> linkageProblems)
3844
throws IOException {
45+
checkNotNull(classPathBuilder);
46+
checkNotNull(rootResult);
47+
checkNotNull(linkageProblems);
3948

4049
Map<Artifact, ClassPathResult> cache = new HashMap<>();
4150
for (LinkageProblem linkageProblem : linkageProblems) {
@@ -47,7 +56,6 @@ public static void annotate(ClassPathResult rootResult, Iterable<LinkageProblem>
4756
ClassPathResult subtreeResult = cache.get(sourceArtifact);
4857
if (subtreeResult == null) {
4958
// Resolves the dependency graph with the source artifact at the root.
50-
ClassPathBuilder classPathBuilder = new ClassPathBuilder();
5159
subtreeResult = classPathBuilder.resolveWithMaven(sourceArtifact);
5260
cache.put(sourceArtifact, subtreeResult);
5361
}

‎dependencies/src/main/java/com/google/cloud/tools/opensource/dependencies/Java8IncompatibleReferenceCheck.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ public static void main(String[] arguments) throws MavenRepositoryException, IOE
105105
.map(ClassPathEntry::getArtifact)
106106
.forEach(artifact -> problematicDependencies.put(managedDependency, artifact));
107107

108-
logger.severe(LinkageProblem.formatLinkageProblems(invalidReferencesToJavaCoreLibrary));
108+
// No need to supply classPath result as the artifact information is output below
109+
logger.severe(
110+
LinkageProblem.formatLinkageProblems(invalidReferencesToJavaCoreLibrary, null));
109111
}
110112
}
111113

‎dependencies/src/test/java/com/google/cloud/tools/opensource/classpath/LinkageProblemCauseAnnotatorTest.java‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
import static org.junit.Assert.assertEquals;
2020
import static org.junit.Assert.assertTrue;
2121

22+
import com.google.cloud.tools.opensource.dependencies.Artifacts;
23+
import com.google.cloud.tools.opensource.dependencies.DependencyGraphBuilder;
2224
import com.google.cloud.tools.opensource.dependencies.DependencyPath;
25+
import com.google.cloud.tools.opensource.dependencies.RepositoryUtility;
2326
import com.google.common.collect.ImmutableList;
2427
import com.google.common.collect.ImmutableSet;
2528
import java.io.IOException;
@@ -30,6 +33,8 @@
3033

3134
public class LinkageProblemCauseAnnotatorTest {
3235

36+
private ClassPathBuilder classPathBuilder = new ClassPathBuilder();
37+
3338
@Test
3439
public void testAnnotate_dom4jOptionalDependency() throws IOException {
3540

@@ -48,7 +53,8 @@ public void testAnnotate_dom4jOptionalDependency() throws IOException {
4853
new ClassFile(dom4jEntry, "org.dom4j.DocumentHelper"),
4954
new ClassSymbol("org.jaxen.VariableContext"));
5055

51-
LinkageProblemCauseAnnotator.annotate(classPathResult, ImmutableSet.of(problem));
56+
LinkageProblemCauseAnnotator.annotate(
57+
classPathBuilder, classPathResult, ImmutableSet.of(problem));
5258

5359
LinkageProblemCause cause = problem.getCause();
5460
assertEquals(MissingDependency.class, cause.getClass());
@@ -81,7 +87,8 @@ public void testAnnotate_googleApiClientAndGrpcConflict() throws IOException {
8187
SymbolNotFoundProblem problem = (SymbolNotFoundProblem) foundProblem.get();
8288
assertEquals("verify", ((MethodSymbol) problem.getSymbol()).getName());
8389

84-
LinkageProblemCauseAnnotator.annotate(classPathResult, ImmutableSet.of(problem));
90+
LinkageProblemCauseAnnotator.annotate(
91+
classPathBuilder, classPathResult, ImmutableSet.of(problem));
8592

8693
LinkageProblemCause cause = problem.getCause();
8794
assertTrue(cause instanceof DependencyConflict);
@@ -125,7 +132,8 @@ public void testAnnotate_autoServiceAnnotationsExclusion() throws IOException {
125132
autoServiceEntry, "com.google.auto.service.processor.AutoServiceProcessor"),
126133
new ClassSymbol("com.google.auto.service.AutoService"));
127134

128-
LinkageProblemCauseAnnotator.annotate(classPathResult, ImmutableSet.of(problem));
135+
LinkageProblemCauseAnnotator.annotate(
136+
classPathBuilder, classPathResult, ImmutableSet.of(problem));
129137

130138
LinkageProblemCause cause = problem.getCause();
131139
assertEquals(ExcludedDependency.class, cause.getClass());
@@ -136,4 +144,48 @@ public void testAnnotate_autoServiceAnnotationsExclusion() throws IOException {
136144

137145
assertEquals("auto-value", excludedDependency.getExcludingArtifact().getArtifactId());
138146
}
147+
148+
@Test
149+
public void testAnnotate_dependencyInSpringRepository() throws IOException {
150+
DependencyGraphBuilder dependencyGraphBuilder =
151+
new DependencyGraphBuilder(
152+
ImmutableList.of(
153+
"https://repo.spring.io/milestone", RepositoryUtility.CENTRAL.getUrl()));
154+
155+
ClassPathBuilder classPathBuilderWithSpring = new ClassPathBuilder(dependencyGraphBuilder);
156+
157+
// io.projectreactor:reactor-core:3.4.0-M2 is in the Spring Milestones repository.
158+
ClassPathResult classPathResult =
159+
classPathBuilderWithSpring.resolve(
160+
ImmutableList.of(
161+
new DefaultArtifact("io.projectreactor:reactor-core:3.4.0-M2"),
162+
new DefaultArtifact("org.reactivestreams:reactive-streams:0.4.0")),
163+
false);
164+
165+
ClassPathEntry reactorCore = classPathResult.getClassPath().get(0);
166+
167+
// A hypothetical problem where org.reactivestreams.Subscriber class is missing because the
168+
// org.reactivestreams:reactive-streams, which contains the class, has a different version.
169+
LinkageProblem problem =
170+
new ClassNotFoundProblem(
171+
new ClassFile(reactorCore, "reactor.util.Metrics"),
172+
new ClassSymbol("org.reactivestreams.Subscriber"));
173+
174+
// This should not throw exception in resolving dependency graph
175+
LinkageProblemCauseAnnotator.annotate(
176+
classPathBuilderWithSpring, classPathResult, ImmutableSet.of(problem));
177+
178+
LinkageProblemCause cause = problem.getCause();
179+
assertEquals(DependencyConflict.class, cause.getClass());
180+
DependencyConflict conflict = (DependencyConflict) cause;
181+
182+
assertEquals(
183+
"org.reactivestreams:reactive-streams:0.4.0",
184+
Artifacts.toCoordinates(conflict.getPathToSelectedArtifact().getLeaf()));
185+
186+
// io.projectreactor:reactor-core depends on reactive-streams 1.0.3
187+
assertEquals(
188+
"org.reactivestreams:reactive-streams:1.0.3",
189+
Artifacts.toCoordinates(conflict.getPathToArtifactThruSource().getLeaf()));
190+
}
139191
}

0 commit comments

Comments
 (0)