Skip to content

Commit bc745fa

Browse files
authored
Use problem information in build failure message (#31374)
2 parents a7fe309 + 23c8dda commit bc745fa

File tree

25 files changed

+309
-221
lines changed

25 files changed

+309
-221
lines changed

platforms/core-runtime/base-services/src/main/java/org/gradle/internal/exceptions/CompilationFailedIndicator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@
1616

1717
package org.gradle.internal.exceptions;
1818

19+
import javax.annotation.Nullable;
20+
1921
public interface CompilationFailedIndicator extends NonGradleCause {
22+
23+
@Nullable
24+
String getDiagnosticCounts();
2025
}

platforms/core-runtime/launcher/src/main/java/org/gradle/launcher/exec/BuildOutcomeReportingBuildActionRunner.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.gradle.api.internal.GradleInternal;
2121
import org.gradle.api.internal.tasks.execution.statistics.TaskExecutionStatisticsEventAdapter;
2222
import org.gradle.api.logging.Logging;
23+
import org.gradle.api.problems.internal.ExceptionProblemRegistry;
2324
import org.gradle.initialization.BuildRequestMetaData;
2425
import org.gradle.internal.buildevents.BuildLogger;
2526
import org.gradle.internal.buildevents.BuildLoggerFactory;
@@ -38,19 +39,23 @@ public class BuildOutcomeReportingBuildActionRunner implements BuildActionRunner
3839
private final BuildRequestMetaData buildRequestMetaData;
3940
private final StyledTextOutputFactory styledTextOutputFactory;
4041
private final BuildLoggerFactory buildLoggerFactory;
42+
private final ExceptionProblemRegistry registry;
4143

4244
public BuildOutcomeReportingBuildActionRunner(StyledTextOutputFactory styledTextOutputFactory,
4345
ListenerManager listenerManager,
4446
BuildActionRunner delegate,
4547
BuildStartedTime buildStartedTime,
4648
BuildRequestMetaData buildRequestMetaData,
47-
BuildLoggerFactory buildLoggerFactory) {
49+
BuildLoggerFactory buildLoggerFactory,
50+
ExceptionProblemRegistry registry
51+
) {
4852
this.styledTextOutputFactory = styledTextOutputFactory;
4953
this.listenerManager = listenerManager;
5054
this.delegate = delegate;
5155
this.buildStartedTime = buildStartedTime;
5256
this.buildRequestMetaData = buildRequestMetaData;
5357
this.buildLoggerFactory = buildLoggerFactory;
58+
this.registry = registry;
5459
}
5560

5661
@Override
@@ -65,7 +70,7 @@ public Result run(BuildAction action, BuildTreeLifecycleController buildControll
6570

6671
Result result = delegate.run(action, buildController);
6772

68-
buildLogger.logResult(result.getBuildFailure());
73+
buildLogger.logResult(result.getBuildFailure(), registry.getProblemLookup());
6974
new TaskExecutionStatisticsReporter(styledTextOutputFactory).buildFinished(taskStatisticsCollector.getStatistics());
7075
return result;
7176
}

platforms/core-runtime/launcher/src/main/java/org/gradle/launcher/exec/RunAsBuildOperationBuildActionExecutor.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import org.gradle.api.NonNullApi;
2020
import org.gradle.api.problems.internal.ExceptionProblemRegistry;
21-
import org.gradle.api.problems.internal.Problem;
21+
import org.gradle.api.problems.internal.ProblemLookup;
2222
import org.gradle.internal.buildtree.BuildActionRunner;
2323
import org.gradle.internal.invocation.BuildAction;
2424
import org.gradle.internal.operations.BuildOperationContext;
@@ -30,9 +30,6 @@
3030
import org.gradle.internal.session.BuildSessionActionExecutor;
3131
import org.gradle.internal.session.BuildSessionContext;
3232

33-
import java.util.Collection;
34-
import java.util.Map;
35-
3633
/**
3734
* An {@link BuildActionRunner} that wraps all work in a build operation.
3835
*/
@@ -79,8 +76,8 @@ public BuildActionRunner.Result call(BuildOperationContext buildOperationContext
7976
public BuildOperationDescriptor.Builder description() {
8077
return BuildOperationDescriptor.displayName("Run build").details(new RunBuildBuildOperationType.Details() {
8178
@Override
82-
public Map<Throwable, Collection<Problem>> getProblemsForThrowables() {
83-
return problemContainer.getProblemsForThrowables();
79+
public ProblemLookup getProblemLookup() {
80+
return problemContainer.getProblemLookup();
8481
}
8582
});
8683
}

platforms/core-runtime/launcher/src/main/java/org/gradle/launcher/exec/RunBuildBuildOperationType.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,14 @@
1616

1717
package org.gradle.launcher.exec;
1818

19-
import org.gradle.api.problems.internal.Problem;
19+
import org.gradle.api.problems.internal.ProblemLookup;
2020
import org.gradle.internal.operations.BuildOperationType;
2121
import org.gradle.internal.scan.UsedByScanPlugin;
2222

23-
import java.util.Collection;
24-
import java.util.Map;
25-
2623
@UsedByScanPlugin
2724
public final class RunBuildBuildOperationType implements BuildOperationType<RunBuildBuildOperationType.Details, RunBuildBuildOperationType.Result> {
2825
public interface Details {
29-
Map<Throwable, Collection<Problem>> getProblemsForThrowables();
26+
ProblemLookup getProblemLookup();
3027
}
3128

3229
public interface Result {

platforms/core-runtime/launcher/src/main/java/org/gradle/tooling/internal/provider/LauncherServices.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ BuildTreeActionExecutor createActionExecutor(
213213
InternalOptions options,
214214
StartParameter startParameter,
215215
InternalProblems problemsService,
216-
ProblemStream problemStream
216+
ProblemStream problemStream,
217+
ExceptionProblemRegistry registry
217218
) {
218219
return new InitProblems(
219220
new InitDeprecationLoggingActionExecutor(
@@ -239,7 +240,9 @@ BuildTreeActionExecutor createActionExecutor(
239240
),
240241
buildStartedTime,
241242
buildRequestMetaData,
242-
buildLoggerFactory),
243+
buildLoggerFactory,
244+
registry
245+
),
243246
options),
244247
gradleEnterprisePluginManager)),
245248
eventEmitter,

platforms/ide/problems-api/src/main/java/org/gradle/api/problems/internal/DefaultProblemReporter.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.gradle.problems.buildtree.ProblemStream;
2525

2626
import javax.annotation.Nonnull;
27+
import java.util.Collection;
2728

2829
public class DefaultProblemReporter implements InternalProblemReporter {
2930

@@ -76,9 +77,30 @@ public RuntimeException throwing(Action<ProblemSpec> spec) {
7677
}
7778
}
7879

80+
@Override
81+
public RuntimeException throwing(Throwable exception, Collection<? extends Problem> problems) {
82+
for (Problem problem : problems) {
83+
Problem problemWithException = new DefaultProblem(
84+
problem.getDefinition(),
85+
problem.getContextualLabel(),
86+
problem.getSolutions(),
87+
problem.getOriginLocations(),
88+
problem.getContextualLocations(),
89+
problem.getDetails(),
90+
transform(exception),
91+
problem.getAdditionalData()
92+
);
93+
report(problemWithException);
94+
}
95+
if (exception instanceof RuntimeException) {
96+
return (RuntimeException) exception;
97+
} else {
98+
throw new RuntimeException(exception);
99+
}
100+
}
101+
79102
private RuntimeException throwError(Throwable exception, Problem problem) {
80103
report(problem);
81-
exceptionProblemRegistry.onProblem(transform(exception), problem);
82104
if (exception instanceof RuntimeException) {
83105
return (RuntimeException) exception;
84106
} else {
@@ -109,6 +131,13 @@ public void report(Problem problem) {
109131
}
110132
}
111133

134+
@Override
135+
public void report(Collection<? extends Problem> problems) {
136+
for (Problem problem : problems) {
137+
report(problem);
138+
}
139+
}
140+
112141
/**
113142
* Reports a problem with an explicit operation identifier.
114143
* <p>

platforms/ide/problems-api/src/main/java/org/gradle/api/problems/internal/ExceptionProblemRegistry.java

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,20 @@
1616

1717
package org.gradle.api.problems.internal;
1818

19-
import com.google.common.collect.HashMultimap;
19+
import com.google.common.collect.ArrayListMultimap;
20+
import com.google.common.collect.ImmutableList;
2021
import com.google.common.collect.Multimap;
22+
import com.google.common.collect.MultimapBuilder;
2123
import com.google.common.collect.Multimaps;
2224
import org.gradle.api.Incubating;
2325
import org.gradle.internal.service.scopes.Scope;
2426
import org.gradle.internal.service.scopes.ServiceScope;
2527

28+
import javax.annotation.Nullable;
29+
import java.util.ArrayList;
2630
import java.util.Collection;
27-
import java.util.Map;
31+
import java.util.List;
32+
import java.util.Set;
2833

2934
/**
3035
* Holds references to exceptions reported via {@link org.gradle.problems.buildtree.ProblemReporter} and their associated problem reports.
@@ -35,14 +40,119 @@
3540
@ServiceScope(Scope.BuildSession.class)
3641
public class ExceptionProblemRegistry {
3742

38-
private final Multimap<Throwable, Problem> problemsForThrowables = Multimaps.synchronizedMultimap(HashMultimap.<Throwable, Problem>create());
39-
43+
private final Multimap<Throwable, Problem> problemsForThrowables = Multimaps.synchronizedMultimap(MultimapBuilder.linkedHashKeys().linkedHashSetValues().<Throwable, Problem>build());
4044

4145
public void onProblem(Throwable exception, Problem problem) {
4246
problemsForThrowables.put(exception, problem);
4347
}
4448

45-
public Map<Throwable, Collection<Problem>> getProblemsForThrowables() {
46-
return problemsForThrowables.asMap();
49+
public ProblemLookup getProblemLookup() {
50+
return new DefaultProblemLookup();
51+
}
52+
53+
/*
54+
* Workaround for the fact that the exception thrown by the worker is not the same instance as the one that was thrown by the build. With the lookup we can find the original exception by comparing
55+
* the stack frames. The comparison is expensive, so we only do when it's necessary (when the original exception does not contain the target and there's a matching class name and message).
56+
*/
57+
private class DefaultProblemLookup implements ProblemLookup {
58+
59+
60+
private final Multimap<String, Throwable> lookup;
61+
62+
DefaultProblemLookup() {
63+
this.lookup = initLookup(problemsForThrowables.keySet());
64+
}
65+
66+
private Multimap<String, Throwable> initLookup(Set<Throwable> exceptions) {
67+
Multimap<String, Throwable> lookup = ArrayListMultimap.create();
68+
for (Throwable exception : exceptions) {
69+
lookup.put(key(exception), exception);
70+
}
71+
return lookup;
72+
}
73+
74+
private String key(Throwable t) {
75+
return t.getClass().getName() + ":" + messageOf(t);
76+
}
77+
78+
private String messageOf(Throwable t) {
79+
String result = "";
80+
try {
81+
String message = t.getMessage();
82+
result = message == null ? "" : message;
83+
} catch (RuntimeException ignore) {
84+
// ignore exceptions with faulty getMessage() implementation
85+
}
86+
return result;
87+
}
88+
89+
@Override
90+
public Collection<Problem> findAll(Throwable t) {
91+
Throwable throwable = find(t);
92+
return throwable == null ? ImmutableList.<Problem>of() : ImmutableList.copyOf(problemsForThrowables.get(throwable));
93+
}
94+
95+
@Nullable
96+
private Throwable find(Throwable t) {
97+
try {
98+
if (problemsForThrowables.keySet().contains(t)) {
99+
return t;
100+
}
101+
Collection<Throwable> candidates = lookup.get(key(t));
102+
for (Throwable candidate : candidates) {
103+
if (deepEquals(candidate, t, new ArrayList<Throwable>())) {
104+
return candidate;
105+
}
106+
}
107+
} catch (RuntimeException e) {
108+
e.printStackTrace();
109+
return null;
110+
}
111+
return null;
112+
}
113+
114+
private boolean deepEquals(Throwable t1, Throwable t2, List<Throwable> seen) {
115+
if (seen.contains(t1) || seen.contains(t2)) {
116+
return false; // drop self-references to avoid infinite recursion
117+
}
118+
119+
if (t1 == null && t2 == null) {
120+
return true; // equals if both null
121+
} else if (t1 == null || t2 == null) {
122+
return false; // either t1 or t2 is null
123+
}
124+
125+
if (!t1.getClass().equals(t2.getClass()) || !messageOf(t1).equals(messageOf(t2))) {
126+
return false;
127+
}
128+
StackTraceElement[] s1 = t1.getStackTrace();
129+
StackTraceElement[] s2 = t2.getStackTrace();
130+
for (int i = 0; i < s1.length && i < s2.length; i++) {
131+
if (!isSackTraceElementEquals(s1[i], s2[i])) {
132+
return false;
133+
}
134+
}
135+
seen.add(t1);
136+
seen.add(t2);
137+
return deepEquals(t1.getCause(), t2.getCause(), seen);
138+
}
139+
140+
private boolean isSackTraceElementEquals(StackTraceElement s1, StackTraceElement s2) {
141+
if (!s1.getClassName().equals(s2.getClassName())) {
142+
return false;
143+
}
144+
String s1File = s1.getFileName();
145+
String s2File = s2.getFileName();
146+
if ((s1File == null && s2File != null) || (s1File != null && s2File == null)) {
147+
return false;
148+
} else if (s1File != null && s2File != null && !s1File.equals(s2File)) {
149+
return false;
150+
} else if (s1.getLineNumber() != s2.getLineNumber()) {
151+
return false;
152+
}
153+
154+
return true;
155+
}
156+
47157
}
48158
}

platforms/ide/problems-api/src/main/java/org/gradle/api/problems/internal/InternalProblemReporter.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.gradle.api.problems.ProblemReporter;
2121
import org.gradle.internal.operations.OperationIdentifier;
2222

23+
import java.util.Collection;
24+
2325
public interface InternalProblemReporter extends ProblemReporter {
2426

2527
/**
@@ -38,11 +40,28 @@ public interface InternalProblemReporter extends ProblemReporter {
3840
*/
3941
void report(Problem problem);
4042

43+
/**
44+
* Reports the target problems.
45+
*
46+
* @param problems The problems to report.
47+
*/
48+
void report(Collection<? extends Problem> problems);
49+
4150
/**
4251
* Reports the target problem with an explicit operation identifier.
4352
*
4453
* @param problem The problem to report.
4554
* @param id The operation identifier.
4655
*/
4756
void report(Problem problem, OperationIdentifier id);
57+
58+
/**
59+
* Reports the target problems and throws a runtime exception. When this method is used, all reported problems will be associated with the thrown exception.
60+
*
61+
* @param exception the exception to throw after reporting the problems
62+
* @param problems the problems to report
63+
* @return nothing, the method throws an exception
64+
* @since 8.12
65+
*/
66+
RuntimeException throwing(Throwable exception, Collection<? extends Problem> problems);
4867
}
Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,8 @@
1616

1717
package org.gradle.api.problems.internal;
1818

19-
import org.gradle.api.NonNullApi;
20-
2119
import java.util.Collection;
2220

23-
/**
24-
* If implemented on an {@link Exception}, this interface will be used to fetch and render
25-
* problems specific to the exception.
26-
*/
27-
@NonNullApi
28-
public interface ProblemAwareFailure {
29-
30-
/**
31-
* Returns a collection of problems that are specific to this exception.
32-
*
33-
* @return a collection of problems
34-
* @since 8.10
35-
*/
36-
Collection<Problem> getProblems();
21+
public interface ProblemLookup {
22+
Collection<Problem> findAll(Throwable t);
3723
}

platforms/ide/problems-rendering/src/main/java/org/gradle/problems/internal/rendering/ProblemRenderer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ static void renderProblem(PrintWriter output, Problem problem) {
7676
}
7777

7878
static void formatMultiline(PrintWriter output, String message, int level) {
79+
if (message == null) {
80+
return;
81+
}
7982
for (String line : message.split("\n")) {
8083
for (int i = 0; i < level; i++) {
8184
output.print(" ");

0 commit comments

Comments
 (0)