diff --git a/src/java/io/bazel/rulesscala/scalac/ScalacInvoker.java b/src/java/io/bazel/rulesscala/scalac/ScalacInvoker.java index e7ebeaccd..4f1e17501 100644 --- a/src/java/io/bazel/rulesscala/scalac/ScalacInvoker.java +++ b/src/java/io/bazel/rulesscala/scalac/ScalacInvoker.java @@ -24,11 +24,11 @@ public static ScalacInvokerResults invokeCompiler(CompileOptions ops, String[] c comp.process(compilerArgs); } catch (Throwable ex) { if (ex.toString().contains("scala.reflect.internal.Types$TypeError")) { - throw new RuntimeException("Build failure with type error", ex); + throw new ScalacWorker.CompilationFailed("with type error", ex); } else if (ex.toString().contains("java.lang.StackOverflowError")) { - throw new RuntimeException("Build failure with StackOverflowError", ex); + throw new ScalacWorker.CompilationFailed("with StackOverflowError", ex); } else if (isMacroException(ex)) { - throw new RuntimeException("Build failure during macro expansion", ex); + throw new ScalacWorker.CompilationFailed("during macro expansion", ex); } else { throw ex; } @@ -39,6 +39,12 @@ public static ScalacInvokerResults invokeCompiler(CompileOptions ops, String[] c results.stopTime = System.currentTimeMillis(); ConsoleReporter reporter = (ConsoleReporter) comp.getReporter(); + if (reporter == null) { + // Can happen only when `ReportableMainClass::newCompiler` was not invoked, + // typically due to invalid settings + throw new ScalacWorker.InvalidSettings(); + } + if (reporter instanceof ProtoReporter) { ProtoReporter protoReporter = (ProtoReporter) reporter; protoReporter.writeTo(Paths.get(ops.diagnosticsFile)); @@ -52,7 +58,7 @@ public static ScalacInvokerResults invokeCompiler(CompileOptions ops, String[] c if (reporter.hasErrors()) { reporter.flush(); - throw new RuntimeException("Build failed"); + throw new ScalacWorker.CompilationFailed("with errors."); } return results; diff --git a/src/java/io/bazel/rulesscala/scalac/ScalacInvoker3.java b/src/java/io/bazel/rulesscala/scalac/ScalacInvoker3.java index 85d655750..9849902d4 100644 --- a/src/java/io/bazel/rulesscala/scalac/ScalacInvoker3.java +++ b/src/java/io/bazel/rulesscala/scalac/ScalacInvoker3.java @@ -22,7 +22,11 @@ public static ScalacInvokerResults invokeCompiler(CompileOptions ops, String[] c Driver driver = new dotty.tools.dotc.Driver(); Contexts.Context ctx = driver.initCtx().fresh(); - Tuple2, Contexts.Context> r = driver.setup(compilerArgs, ctx).get(); + Tuple2, Contexts.Context> r = + driver.setup(compilerArgs, ctx) + .getOrElse(() -> { + throw new ScalacWorker.InvalidSettings(); + }); Compiler compiler = driver.newCompiler(r._2); @@ -39,8 +43,8 @@ public static ScalacInvokerResults invokeCompiler(CompileOptions ops, String[] c if (reporter.hasErrors()) { -// reporter.flush(); - throw new RuntimeException("Build failed"); + reporter.flush(ctx); + throw new ScalacWorker.CompilationFailed("with errors."); } return results; diff --git a/src/java/io/bazel/rulesscala/scalac/ScalacWorker.java b/src/java/io/bazel/rulesscala/scalac/ScalacWorker.java index 6cca6e6b3..6a6679762 100644 --- a/src/java/io/bazel/rulesscala/scalac/ScalacWorker.java +++ b/src/java/io/bazel/rulesscala/scalac/ScalacWorker.java @@ -26,6 +26,21 @@ class ScalacWorker implements Worker.Interface { + public static class InvalidSettings extends WorkerException { + public InvalidSettings() { + super("Failed to invoke Scala compiler, ensure passed options are valid"); + } + } + + public static class CompilationFailed extends WorkerException { + public CompilationFailed(String reason, Throwable cause) { + super("Build failure " + reason, cause); + } + public CompilationFailed(String reason) { + this(reason, null); + } + } + private static final boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); diff --git a/src/java/io/bazel/rulesscala/worker/Worker.java b/src/java/io/bazel/rulesscala/worker/Worker.java index 38fe08a52..e5ed754bb 100644 --- a/src/java/io/bazel/rulesscala/worker/Worker.java +++ b/src/java/io/bazel/rulesscala/worker/Worker.java @@ -27,6 +27,16 @@ public final class Worker { public static interface Interface { public void work(String[] args) throws Exception; + + + public abstract class WorkerException extends RuntimeException { + public WorkerException(String message) { + super(message); + } + public WorkerException(String message, Throwable cause) { + super(message, cause); + } + } } /** @@ -87,8 +97,8 @@ public void checkPermission(Permission permission) { } catch (ExitTrapped e) { code = e.code; } catch (Exception e) { - System.err.println(e.getMessage()); - e.printStackTrace(); + if (e instanceof Interface.WorkerException) System.err.println(e.getMessage()); + else e.printStackTrace(); code = 1; } diff --git a/test/shell/test_invalid_scalacopts.sh b/test/shell/test_invalid_scalacopts.sh new file mode 100755 index 000000000..7108de058 --- /dev/null +++ b/test/shell/test_invalid_scalacopts.sh @@ -0,0 +1,36 @@ +# shellcheck source=./test_runner.sh + +dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +. "${dir}"/test_runner.sh +. "${dir}"/test_helper.sh +runner=$(get_test_runner "${1:-local}") + +test_logs_contains() { + scalaVersion=$1 + expected=$2 + + bazel build \ + --repo_env=SCALA_VERSION=${scalaVersion} \ + //test_expect_failure/scalacopts_invalid:empty \ + 2>&1 | grep "$expected" +} + +test_logs_not_contains() { + scalaVersion=$1 + expected=$2 + + bazel build \ + --repo_env=SCALA_VERSION=${scalaVersion} \ + //test_expect_failure/scalacopts_invalid:empty \ + 2>&1 | grep -v "$expected" +} + +for scalaVersion in 2.12.19 2.13.14 3.3.3; do + if [[ "$scalaVersion" == 3.* ]]; then + $runner test_logs_contains $scalaVersion "not-existing is not a valid choice for -source" + else + $runner test_logs_contains $scalaVersion "bad option: '-source:not-existing'" + fi + $runner test_logs_contains $scalaVersion 'Failed to invoke Scala compiler, ensure passed options are valid' + $runner test_logs_not_contains $scalaVersion 'at io.bazel.rulesscala.scalac.ScalacWorker.main' +done diff --git a/test_expect_failure/scalacopts_invalid/BUILD b/test_expect_failure/scalacopts_invalid/BUILD new file mode 100644 index 000000000..ffca0daf7 --- /dev/null +++ b/test_expect_failure/scalacopts_invalid/BUILD @@ -0,0 +1,7 @@ +load("//scala:scala.bzl", "scala_library") + +scala_library( + name = "empty", + srcs = ["Empty.scala"], + scalacopts = ["-source:not-existing"], +) diff --git a/test_expect_failure/scalacopts_invalid/Empty.scala b/test_expect_failure/scalacopts_invalid/Empty.scala new file mode 100644 index 000000000..d56b3e88f --- /dev/null +++ b/test_expect_failure/scalacopts_invalid/Empty.scala @@ -0,0 +1,3 @@ +package test_expect_failure.scalacopts_invalid + +class Empty diff --git a/test_rules_scala.sh b/test_rules_scala.sh index 05600140c..554da4735 100755 --- a/test_rules_scala.sh +++ b/test_rules_scala.sh @@ -58,3 +58,4 @@ $runner bazel build //test_statsfile:SimpleNoStatsFile_statsfile --extra_toolcha . "${test_dir}"/test_persistent_worker.sh . "${test_dir}"/test_semanticdb.sh . "${test_dir}"/test_scaladoc.sh +. "${test_dir}"/test_invalid_scalacopts.sh