Skip to content

Commit 91a45f7

Browse files
Collect diagnostics in Scala 3 (#1611)
* Add ProtoReporter and DepsTrackingReporter for Scala3. Collect diagnostics in Scala 3 * Lint + rename ConsoleReporter -> BazelConsoleReporter to disambigiuate with dotc.reporting.ConsoleReporter * Revert spuriously commented out tests in `test_version.sh`
1 parent 8f69486 commit 91a45f7

20 files changed

+703
-75
lines changed

src/java/io/bazel/rulesscala/scalac/BUILD

+2-7
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,8 @@ filegroup(
5151
"ScalacWorker.java",
5252
"ScalacInvokerResults.java",
5353
] + select_for_scala_version(
54-
any_2 = [
55-
"ScalacInvoker.java",
56-
"ReportableMainClass.java",
57-
],
58-
any_3 = [
59-
"ScalacInvoker3.java",
60-
],
54+
any_2 = glob(["scala_2/*.java"]),
55+
any_3 = glob(["scala_3/*.java"]),
6156
),
6257
visibility = ["//visibility:public"],
6358
)

src/java/io/bazel/rulesscala/scalac/deps_tracking_reporter/BUILD

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ load("//scala:scala_cross_version_select.bzl", "select_for_scala_version")
33
filegroup(
44
name = "deps_tracking_reporter",
55
srcs = select_for_scala_version(
6+
any_3 = ["scala_3/DepsTrackingReporter.java"],
67
before_2_12_13 = ["before_2_12_13/DepsTrackingReporter.java"],
78
between_2_12_13_and_2_13_12 = ["after_2_12_13_and_before_2_13_12/DepsTrackingReporter.java"],
8-
since_2_13_12 = ["after_2_13_12/DepsTrackingReporter.java"],
9+
between_2_13_12_and_3 = ["after_2_13_12/DepsTrackingReporter.java"],
910
),
1011
visibility = ["//visibility:public"],
1112
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
package io.bazel.rulesscala.scalac.reporter;
2+
3+
import dotty.tools.dotc.core.*;
4+
import dotty.tools.dotc.reporting.*;
5+
import dotty.tools.dotc.util.NoSourcePosition$;
6+
import io.bazel.rulesscala.deps.proto.ScalaDeps;
7+
import io.bazel.rulesscala.deps.proto.ScalaDeps.Dependency;
8+
import io.bazel.rulesscala.deps.proto.ScalaDeps.Dependency.Kind;
9+
import io.bazel.rulesscala.scalac.compileoptions.CompileOptions;
10+
11+
import java.io.BufferedOutputStream;
12+
import java.io.IOException;
13+
import java.io.OutputStream;
14+
import java.nio.file.Files;
15+
import java.nio.file.Paths;
16+
import java.util.*;
17+
import java.util.jar.JarFile;
18+
import java.util.stream.Collectors;
19+
20+
public class DepsTrackingReporter extends BazelConsoleReporter {
21+
22+
private static final String HJAR_JAR_SUFFIX = "-hjar.jar";
23+
private static final String IJAR_JAR_SUFFIX = "-ijar.jar";
24+
private final Set<String> usedJars = new HashSet<>();
25+
26+
private final Map<String, String> jarToTarget = new HashMap<>();
27+
private final Map<String, String> indirectJarToTarget = new HashMap<>();
28+
29+
private final Set<String> ignoredTargets;
30+
private final Set<String> directTargets;
31+
32+
private final CompileOptions ops;
33+
public final Reporter delegateReporter;
34+
private Set<String> astUsedJars = new HashSet<>();
35+
36+
public DepsTrackingReporter(CompileOptions ops, Reporter delegate) {
37+
super();
38+
this.ops = ops;
39+
this.delegateReporter = delegate;
40+
41+
if (ops.directJars.length == ops.directTargets.length) {
42+
for (int i = 0; i < ops.directJars.length; i++) {
43+
jarToTarget.put(ops.directJars[i], ops.directTargets[i]);
44+
}
45+
} else {
46+
throw new IllegalArgumentException(
47+
"mismatched size: directJars " + ops.directJars.length + " vs directTargets"
48+
+ ops.directTargets.length);
49+
}
50+
51+
if (ops.indirectJars.length == ops.indirectTargets.length) {
52+
for (int i = 0; i < ops.indirectJars.length; i++) {
53+
indirectJarToTarget.put(ops.indirectJars[i], ops.indirectTargets[i]);
54+
}
55+
} else {
56+
throw new IllegalArgumentException(
57+
"mismatched size: indirectJars " + ops.directJars.length + " vs indirectTargets "
58+
+ ops.directTargets.length);
59+
}
60+
61+
ignoredTargets = Arrays.stream(ops.unusedDepsIgnoredTargets).collect(Collectors.toSet());
62+
directTargets = Arrays.stream(ops.directTargets).collect(Collectors.toSet());
63+
}
64+
65+
private boolean isDependencyTrackingOn() {
66+
return "ast-plus".equals(ops.dependencyTrackingMethod)
67+
&& (!"off".equals(ops.strictDepsMode) || !"off".equals(ops.unusedDependencyCheckerMode));
68+
}
69+
70+
71+
@Override
72+
public void doReport(Diagnostic dia, Contexts.Context ctx) {
73+
if (dia.message().startsWith("DT:")) {
74+
if (isDependencyTrackingOn()) {
75+
parseOpenedJar(dia.message());
76+
}
77+
} else {
78+
if (delegateReporter != null) {
79+
delegateReporter.doReport(dia, ctx);
80+
} else {
81+
super.doReport(dia, ctx);
82+
}
83+
}
84+
}
85+
86+
private void parseOpenedJar(String msg) {
87+
String jar = msg.split(":")[1];
88+
89+
//normalize path separators (scalac passes os-specific path separators.)
90+
jar = jar.replace("\\", "/");
91+
92+
// track only jars from dependency targets
93+
// this should exclude things like rt.jar which come from JDK
94+
if (jarToTarget.containsKey(jar) || indirectJarToTarget.containsKey(jar)) {
95+
usedJars.add(jar);
96+
}
97+
}
98+
99+
public void prepareReport(Contexts.Context ctx) throws IOException {
100+
Set<String> usedTargets = new HashSet<>();
101+
Set<Dependency> usedDeps = new HashSet<>();
102+
103+
for (String jar : usedJars) {
104+
String target = jarToTarget.get(jar);
105+
106+
if (target == null) {
107+
target = indirectJarToTarget.get(jar);
108+
}
109+
110+
if (target.startsWith("Unknown")) {
111+
target = jarLabel(jar);
112+
}
113+
114+
if (target == null) {
115+
// probably a bug if we get here
116+
continue;
117+
}
118+
119+
Dependency dep = buildDependency(
120+
jar,
121+
target,
122+
astUsedJars.contains(jar) ? Kind.EXPLICIT : Kind.IMPLICIT,
123+
ignoredTargets.contains(target)
124+
);
125+
126+
usedTargets.add(target);
127+
usedDeps.add(dep);
128+
}
129+
130+
Set<Dependency> unusedDeps = new HashSet<>();
131+
if (!hasErrors()) {
132+
for (int i = 0; i < ops.directTargets.length; i++) {
133+
String directTarget = ops.directTargets[i];
134+
if (usedTargets.contains(directTarget)) {
135+
continue;
136+
}
137+
138+
unusedDeps.add(
139+
buildDependency(
140+
ops.directJars[i],
141+
directTarget,
142+
Kind.UNUSED,
143+
ignoredTargets.contains(directTarget) || "off".equals(ops.unusedDependencyCheckerMode)
144+
)
145+
);
146+
}
147+
}
148+
149+
writeSdepsFile(usedDeps, unusedDeps);
150+
151+
Reporter reporter = this.delegateReporter != null ? this.delegateReporter : this;
152+
reportDeps(usedDeps, unusedDeps, reporter, ctx);
153+
}
154+
155+
private Dependency buildDependency(String jar, String target, Kind kind, boolean ignored) {
156+
Dependency.Builder dependecyBuilder = Dependency.newBuilder();
157+
158+
dependecyBuilder.setKind(kind);
159+
dependecyBuilder.setLabel(target);
160+
dependecyBuilder.setIjarPath(jar);
161+
dependecyBuilder.setPath(guessFullJarPath(jar));
162+
dependecyBuilder.setIgnored(ignored);
163+
164+
return dependecyBuilder.build();
165+
}
166+
167+
private void writeSdepsFile(Collection<Dependency> usedDeps, Collection<Dependency> unusedDeps)
168+
throws IOException {
169+
170+
ScalaDeps.Dependencies.Builder builder = ScalaDeps.Dependencies.newBuilder();
171+
builder.setRuleLabel(ops.currentTarget);
172+
builder.setDependencyTrackingMethod(ops.dependencyTrackingMethod);
173+
builder.addAllDependency(usedDeps);
174+
builder.addAllDependency(unusedDeps);
175+
176+
try (OutputStream outputStream = new BufferedOutputStream(
177+
Files.newOutputStream(Paths.get(ops.scalaDepsFile)))) {
178+
outputStream.write(builder.build().toByteArray());
179+
}
180+
}
181+
182+
private void reportDeps(Collection<Dependency> usedDeps, Collection<Dependency> unusedDeps,
183+
Reporter reporter, Contexts.Context ctx) {
184+
if (ops.dependencyTrackingMethod.equals("ast-plus")) {
185+
186+
if (!ops.strictDepsMode.equals("off")) {
187+
boolean isWarning = ops.strictDepsMode.equals("warn");
188+
StringBuilder strictDepsReport = new StringBuilder("Missing strict dependencies:\n");
189+
StringBuilder compilerDepsReport = new StringBuilder("Missing compiler dependencies:\n");
190+
int strictDepsCount = 0;
191+
int compilerDepsCount = 0;
192+
for (Dependency dep : usedDeps) {
193+
String depReport = addDepMessage(dep);
194+
if (dep.getIgnored()) {
195+
continue;
196+
}
197+
198+
if (directTargets.contains(dep.getLabel())) {
199+
continue;
200+
}
201+
202+
if (dep.getKind() == Kind.EXPLICIT) {
203+
strictDepsCount++;
204+
strictDepsReport
205+
.append(isWarning ? "warning: " : "error: ")
206+
.append(depReport);
207+
} else {
208+
compilerDepsCount++;
209+
compilerDepsReport
210+
.append(isWarning ? "warning: " : "error: ")
211+
.append(depReport);
212+
}
213+
}
214+
215+
if (strictDepsCount > 0) {
216+
Message msg = CompilerCompat.toMessage(strictDepsReport.toString());
217+
if (ops.strictDepsMode.equals("warn")) {
218+
reporter.report(new Diagnostic.Warning(msg, NoSourcePosition$.MODULE$), ctx);
219+
} else {
220+
reporter.report(new Diagnostic.Error(msg ,NoSourcePosition$.MODULE$), ctx);
221+
}
222+
}
223+
224+
if (!ops.compilerDepsMode.equals("off") && compilerDepsCount > 0) {
225+
Message msg = CompilerCompat.toMessage(compilerDepsReport.toString());
226+
if (ops.compilerDepsMode.equals("warn")) {
227+
reporter.report(new Diagnostic.Warning(msg, NoSourcePosition$.MODULE$), ctx);
228+
} else {
229+
reporter.report(new Diagnostic.Error(msg ,NoSourcePosition$.MODULE$), ctx);
230+
}
231+
}
232+
}
233+
234+
if (!ops.unusedDependencyCheckerMode.equals("off")) {
235+
boolean isWarning = ops.unusedDependencyCheckerMode.equals("warn");
236+
StringBuilder unusedDepsReport = new StringBuilder("Unused dependencies:\n");
237+
int count = 0;
238+
for (Dependency dep : unusedDeps) {
239+
if (dep.getIgnored()) {
240+
continue;
241+
}
242+
count++;
243+
unusedDepsReport
244+
.append(isWarning ? "warning: " : "error: ")
245+
.append(removeDepMessage(dep));
246+
}
247+
if (count > 0) {
248+
Message msg = CompilerCompat.toMessage(unusedDepsReport.toString());
249+
if (isWarning) {
250+
reporter.report(new Diagnostic.Warning(msg, NoSourcePosition$.MODULE$), ctx);
251+
} else {
252+
reporter.report(new Diagnostic.Error(msg ,NoSourcePosition$.MODULE$), ctx);
253+
}
254+
}
255+
}
256+
}
257+
}
258+
259+
private String addDepMessage(Dependency dep) {
260+
String target = dep.getLabel();
261+
String jar = dep.getPath();
262+
263+
String message = "Target '" + target + "' (via jar: ' " + jar + " ') "
264+
+ "is being used by " + ops.currentTarget
265+
+ " but is is not specified as a dependency, please add it to the deps.\n"
266+
+ "You can use the following buildozer command:\n";
267+
String command = "buildozer 'add deps " + target + "' " + ops.currentTarget + "\n";
268+
return message + command;
269+
}
270+
271+
private String removeDepMessage(Dependency dep) {
272+
String target = dep.getLabel();
273+
String jar = dep.getPath();
274+
275+
String message = "Target '" + target + "' (via jar: ' " + jar + " ') "
276+
+ "is specified as a dependency to " + ops.currentTarget
277+
+ " but isn't used, please remove it from the deps.\n"
278+
+ "You can use the following buildozer command:\n";
279+
String command = "buildozer 'remove deps " + target + "' " + ops.currentTarget + "\n";
280+
281+
return message + command;
282+
}
283+
284+
private String guessFullJarPath(String jar) {
285+
if (jar.endsWith(IJAR_JAR_SUFFIX)) {
286+
return stripIjarSuffix(jar, IJAR_JAR_SUFFIX);
287+
} else if (jar.endsWith(HJAR_JAR_SUFFIX)) {
288+
return stripIjarSuffix(jar, HJAR_JAR_SUFFIX);
289+
} else {
290+
return jar;
291+
}
292+
}
293+
294+
private static String stripIjarSuffix(String jar, String suffix) {
295+
return jar.substring(0, jar.length() - suffix.length()) + ".jar";
296+
}
297+
298+
private String jarLabel(String path) throws IOException {
299+
try (JarFile jar = new JarFile(path)) {
300+
return jar.getManifest().getMainAttributes().getValue("Target-Label");
301+
}
302+
}
303+
304+
public void registerAstUsedJars(Set<String> jars) {
305+
astUsedJars = jars;
306+
}
307+
308+
public void writeDiagnostics(String diagnosticsFile) throws IOException {
309+
if (! (delegateReporter instanceof ProtoReporter)) {
310+
return;
311+
}
312+
313+
ProtoReporter protoReporter = (ProtoReporter) delegateReporter;
314+
protoReporter.writeTo(Paths.get(diagnosticsFile));
315+
}
316+
}

src/java/io/bazel/rulesscala/scalac/reporter/BUILD

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
load("@rules_proto//proto:defs.bzl", "proto_library")
21
load("@rules_java//java:defs.bzl", "java_library", "java_proto_library")
2+
load("@rules_proto//proto:defs.bzl", "proto_library")
33
load("//scala:scala_cross_version_select.bzl", "select_for_scala_version")
44

55
java_library(
@@ -17,7 +17,14 @@ java_library(
1717
"//src/java/io/bazel/rulesscala/scalac/deps_tracking_reporter",
1818
"after_2_13_12/ProtoReporter.java",
1919
],
20-
default = ["PlaceholderForEmptyScala3Lib.java"],
20+
between_3_0_and_3_3 = glob(["scala_3/*.java"]) + [
21+
"since_3_before_3_3/CompilerCompat.java",
22+
"//src/java/io/bazel/rulesscala/scalac/deps_tracking_reporter",
23+
],
24+
since_3_3 = glob(["scala_3/*.java"]) + [
25+
"since_3_3/CompilerCompat.java",
26+
"//src/java/io/bazel/rulesscala/scalac/deps_tracking_reporter",
27+
],
2128
),
2229
visibility = ["//visibility:public"],
2330
deps = [

src/java/io/bazel/rulesscala/scalac/reporter/PlaceholderForEmptyScala3Lib.java

-5
This file was deleted.

0 commit comments

Comments
 (0)