Skip to content

Commit eb63ea0

Browse files
authored
Fix diagnostic outputs for Scala 2.12.13 (#1532)
1 parent f8274d6 commit eb63ea0

File tree

12 files changed

+395
-105
lines changed

12 files changed

+395
-105
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ hash2
99
.metals
1010
.vscode
1111
unformatted-*.backup.scala
12+
.scala-build

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

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

test/diagnostics_reporter/BUILD

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
load("@rules_java//java:defs.bzl", "java_binary")
2-
load("@io_bazel_rules_scala_config//:config.bzl", "SCALA_MAJOR_VERSION", "SCALA_MINOR_VERSION")
32

43
java_binary(
54
name = "diagnostics_reporter_test",
65
srcs = [
7-
"before_2_13_12/DiagnosticsReporterTest.java",
8-
"VerifyDiagnosticsOutput.java",
9-
] if SCALA_MAJOR_VERSION.startswith("2.11") or SCALA_MAJOR_VERSION.startswith("2.12") or (SCALA_MAJOR_VERSION.startswith("2.13") and int(SCALA_MINOR_VERSION) < 12) else [
10-
"after_2_13_12/DiagnosticsReporterTest.java",
6+
"DiagnosticsReporterTest.java",
117
"VerifyDiagnosticsOutput.java",
128
],
139
main_class = "diagnostics_reporter.DiagnosticsReporterTest",

0 commit comments

Comments
 (0)