Skip to content

Commit 5185d4d

Browse files
committed
[sbt-bridge] Upgrade to CompilerInterface2
1 parent 77e053d commit 5185d4d

19 files changed

+488
-212
lines changed

compiler/src/dotty/tools/dotc/Driver.scala

+34-18
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import core.Contexts._
99
import core.{MacroClassLoader, Mode, TypeError}
1010
import core.StdNames.nme
1111
import dotty.tools.dotc.ast.Positioned
12-
import dotty.tools.io.File
12+
import dotty.tools.io.{File, AbstractFile}
1313
import reporting._
1414
import core.Decorators._
1515
import config.Feature
16+
import util.SourceFile
1617

1718
import scala.util.control.NonFatal
1819
import fromtasty.{TASTYCompiler, TastyFileUtil}
@@ -31,26 +32,14 @@ class Driver {
3132

3233
protected def emptyReporter: Reporter = new StoreReporter(null)
3334

34-
protected def doCompile(compiler: Compiler, fileNames: List[String])(using Context): Reporter =
35-
if (fileNames.nonEmpty)
35+
protected def doCompile(compiler: Compiler, fileNames: List[String])(using ctx: Context): Reporter =
36+
if fileNames.nonEmpty then
3637
try
3738
val run = compiler.newRun
3839
run.compile(fileNames)
39-
40-
def finish(run: Run)(using Context): Unit =
41-
run.printSummary()
42-
if !ctx.reporter.errorsReported && run.suspendedUnits.nonEmpty then
43-
val suspendedUnits = run.suspendedUnits.toList
44-
if (ctx.settings.XprintSuspension.value)
45-
report.echo(i"compiling suspended $suspendedUnits%, %")
46-
val run1 = compiler.newRun
47-
for unit <- suspendedUnits do unit.suspended = false
48-
run1.compileUnits(suspendedUnits)
49-
finish(run1)(using MacroClassLoader.init(ctx.fresh))
50-
51-
finish(run)
40+
finish(compiler, run)
5241
catch
53-
case ex: FatalError =>
42+
case ex: FatalError =>
5443
report.error(ex.getMessage) // signals that we should fail compilation.
5544
case ex: TypeError =>
5645
println(s"${ex.toMessage} while compiling ${fileNames.mkString(", ")}")
@@ -59,7 +48,34 @@ class Driver {
5948
println(s"$ex while compiling ${fileNames.mkString(", ")}")
6049
throw ex
6150
ctx.reporter
62-
end doCompile
51+
52+
protected def doCompileFiles(compiler: Compiler, files: List[AbstractFile])(using Context): Reporter =
53+
if files.nonEmpty then
54+
try
55+
val run = compiler.newRun
56+
run.compileFiles(files)
57+
finish(compiler, run)
58+
catch
59+
case ex: FatalError =>
60+
report.error(ex.getMessage) // signals that we should fail compilation.
61+
case ex: TypeError =>
62+
println(s"${ex.toMessage} while compiling ${files.map(_.path).mkString(", ")}")
63+
throw ex
64+
case ex: Throwable =>
65+
println(s"$ex while compiling ${files.map(_.path).mkString(", ")}")
66+
throw ex
67+
ctx.reporter
68+
69+
protected def finish(compiler: Compiler, run: Run)(using Context): Unit =
70+
run.printSummary()
71+
if !ctx.reporter.errorsReported && run.suspendedUnits.nonEmpty then
72+
val suspendedUnits = run.suspendedUnits.toList
73+
if (ctx.settings.XprintSuspension.value)
74+
report.echo(i"compiling suspended $suspendedUnits%, %")
75+
val run1 = compiler.newRun
76+
for unit <- suspendedUnits do unit.suspended = false
77+
run1.compileUnits(suspendedUnits)
78+
finish(compiler, run1)(using MacroClassLoader.init(ctx.fresh))
6379

6480
protected def initCtx: Context = (new ContextBase).initialCtx
6581

compiler/src/dotty/tools/dotc/Run.scala

+14-12
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,13 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
124124
/** Actions that need to be performed at the end of the current compilation run */
125125
private var finalizeActions = mutable.ListBuffer[() => Unit]()
126126

127-
def compile(fileNames: List[String]): Unit = try {
127+
def compile(fileNames: List[String]): Unit =
128128
val sources = fileNames.map(runContext.getSource(_))
129129
compileSources(sources)
130-
}
131-
catch {
132-
case NonFatal(ex) =>
133-
if units != null then report.echo(i"exception occurred while compiling $units%, %")
134-
else report.echo(s"exception occurred while compiling ${fileNames.mkString(", ")}")
135-
throw ex
136-
}
130+
131+
def compileFiles(files: List[AbstractFile]): Unit =
132+
val sources = files.map(runContext.getSource(_))
133+
compileSources(sources)
137134

138135
/** TODO: There's a fundamental design problem here: We assemble phases using `fusePhases`
139136
* when we first build the compiler. But we modify them with -Yskip, -Ystop
@@ -142,10 +139,15 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
142139
* account. I think the latter would be preferable.
143140
*/
144141
def compileSources(sources: List[SourceFile]): Unit =
145-
if (sources forall (_.exists)) {
146-
units = sources.map(CompilationUnit(_))
147-
compileUnits()
148-
}
142+
try
143+
if sources forall (_.exists) then
144+
units = sources.map(CompilationUnit(_))
145+
compileUnits()
146+
catch
147+
case NonFatal(ex) =>
148+
if units != null then report.echo(i"exception occurred while compiling $units%, %")
149+
else report.echo(s"exception occurred while compiling ${sources.map(_.name).mkString(", ")}")
150+
throw ex
149151

150152
def compileUnits(us: List[CompilationUnit]): Unit = {
151153
units = us

compiler/src/dotty/tools/dotc/config/CompilerCommand.scala

+6-5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ object CompilerCommand {
3333

3434
def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"
3535

36+
def shouldStopWithInfo(using ctx: Context) = {
37+
val settings = ctx.settings
38+
import settings._
39+
Set(help, Xhelp, Yhelp, showPlugins, XshowPhases) exists (_.value)
40+
}
41+
3642
/** Distill arguments into summary detailing settings, errors and files to compiler */
3743
def distill(args: Array[String])(using Context): ArgsSummary = {
3844
/**
@@ -110,11 +116,6 @@ object CompilerCommand {
110116
def xusageMessage = createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
111117
def yusageMessage = createUsageMsg("Possible private", shouldExplain = true, isPrivate)
112118

113-
def shouldStopWithInfo = {
114-
import settings._
115-
Set(help, Xhelp, Yhelp, showPlugins, XshowPhases) exists (_.value)
116-
}
117-
118119
def phasesMessage: String = {
119120
(new Compiler()).phases.map {
120121
case List(single) => single.phaseName

project/Build.scala

+6-5
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ object Build {
490490
// get libraries onboard
491491
libraryDependencies ++= Seq(
492492
"org.scala-lang.modules" % "scala-asm" % "7.3.1-scala-1", // used by the backend
493-
Dependencies.`compiler-interface`,
493+
Dependencies.oldCompilerInterface, // we stick to the old version to avoid deprecation warnings
494494
"org.jline" % "jline-reader" % "3.15.0", // used by the REPL
495495
"org.jline" % "jline-terminal" % "3.15.0",
496496
"org.jline" % "jline-terminal-jna" % "3.15.0" // needed for Windows
@@ -953,12 +953,14 @@ object Build {
953953
sources in Test := Seq(),
954954
scalaSource in Compile := baseDirectory.value,
955955
javaSource in Compile := baseDirectory.value,
956+
resourceDirectory in Compile := baseDirectory.value.getParentFile / "resources",
956957

957958
// Referring to the other project using a string avoids an infinite loop
958959
// when sbt reads the settings.
959960
test in Test := (test in (LocalProject("scala3-sbt-bridge-tests"), Test)).value,
960961

961-
libraryDependencies += Dependencies.`compiler-interface` % Provided
962+
// The `newCompilerInterface` is backward compatible with the `oldCompilerInterface`
963+
libraryDependencies += Dependencies.newCompilerInterface % Provided
962964
)
963965

964966
// We use a separate project for the bridge tests since they can only be run
@@ -973,8 +975,7 @@ object Build {
973975

974976
// Tests disabled until zinc-api-info cross-compiles with 2.13,
975977
// alternatively we could just copy in sources the part of zinc-api-info we need.
976-
sources in Test := Seq(),
977-
// libraryDependencies += (Dependencies.`zinc-api-info` % Test).withDottyCompat(scalaVersion.value)
978+
sources in Test := Seq()
978979
)
979980

980981
lazy val `scala3-language-server` = project.in(file("language-server")).
@@ -1239,7 +1240,7 @@ object Build {
12391240
// Keep in sync with inject-sbt-dotty.sbt
12401241
libraryDependencies ++= Seq(
12411242
Dependencies.`jackson-databind`,
1242-
Dependencies.`compiler-interface`
1243+
Dependencies.newCompilerInterface
12431244
),
12441245
unmanagedSourceDirectories in Compile +=
12451246
baseDirectory.value / "../language-server/src/dotty/tools/languageserver/config",

project/Dependencies.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ object Dependencies {
1010
val `jackson-dataformat-yaml` =
1111
"com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % jacksonVersion
1212

13-
private val zincVersion = "1.2.5"
14-
val `compiler-interface` = "org.scala-sbt" % "compiler-interface" % zincVersion
15-
val `zinc-api-info` = "org.scala-sbt" %% "zinc-apiinfo" % zincVersion
13+
val newCompilerInterface = "org.scala-sbt" % "compiler-interface" % "1.4.3"
14+
val oldCompilerInterface = "org.scala-sbt" % "compiler-interface" % "1.3.5"
1615
}

project/build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ unmanagedSourceDirectories in Compile += baseDirectory.value / "../sbt-dotty/src
1010
// Keep in sync with `sbt-dotty` config in Build.scala
1111
libraryDependencies ++= Seq(
1212
Dependencies.`jackson-databind`,
13-
Dependencies.`compiler-interface`
13+
Dependencies.newCompilerInterface
1414
)
1515
unmanagedSourceDirectories in Compile +=
1616
baseDirectory.value / "../language-server/src/dotty/tools/languageserver/config"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dotty.tools.xsbt.CompilerBridge
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Zinc - The incremental compiler for Scala.
3+
* Copyright Lightbend, Inc. and Mark Harrah
4+
*/
5+
6+
package dotty.tools.xsbt;
7+
8+
import xsbti.AnalysisCallback;
9+
import xsbti.Logger;
10+
import xsbti.Reporter;
11+
import xsbti.VirtualFile;
12+
import xsbti.compile.CompileProgress;
13+
import xsbti.compile.CompilerInterface2;
14+
import xsbti.compile.DependencyChanges;
15+
import xsbti.compile.Output;
16+
17+
public final class CompilerBridge implements CompilerInterface2 {
18+
@Override
19+
public void run(VirtualFile[] sources, DependencyChanges changes, String[] options, Output output,
20+
AnalysisCallback callback, Reporter delegate, CompileProgress progress, Logger log) {
21+
CompilerBridgeDriver driver = new CompilerBridgeDriver(options, output);
22+
driver.run(sources, callback, log, delegate);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Zinc - The incremental compiler for Scala.
3+
* Copyright Lightbend, Inc. and Mark Harrah
4+
*/
5+
6+
package dotty.tools.xsbt;
7+
8+
import dotty.tools.dotc.Compiler;
9+
import dotty.tools.dotc.Driver;
10+
import dotty.tools.dotc.config.CompilerCommand;
11+
import dotty.tools.dotc.config.Properties;
12+
import dotty.tools.dotc.core.Contexts;
13+
import dotty.tools.io.AbstractFile;
14+
import scala.collection.mutable.ListBuffer;
15+
import scala.io.Codec;
16+
import xsbti.Problem;
17+
import xsbti.*;
18+
import xsbti.compile.Output;
19+
20+
import java.io.IOException;
21+
import java.util.Comparator;
22+
import java.util.Arrays;
23+
24+
public class CompilerBridgeDriver extends Driver {
25+
private final String[] scalacOptions;
26+
private final String[] args;
27+
28+
public CompilerBridgeDriver(String[] scalacOptions, Output output) {
29+
super();
30+
this.scalacOptions = scalacOptions;
31+
32+
if (!output.getSingleOutput().isPresent())
33+
throw new IllegalArgumentException("output should be a SingleOutput, was a " + output.getClass().getName());
34+
35+
this.args = new String[scalacOptions.length + 2];
36+
System.arraycopy(scalacOptions, 0, args, 0, scalacOptions.length);
37+
args[scalacOptions.length] = "-d";
38+
args[scalacOptions.length + 1] = output.getSingleOutput().get().getAbsolutePath();
39+
}
40+
41+
private static final String StopInfoError =
42+
"Compiler option supplied that disabled Zinc compilation.";
43+
44+
/**
45+
* `sourcesRequired` is set to false because the context is set up with no sources
46+
* The sources are passed programmatically to the compiler in the form of AbstractFiles
47+
*/
48+
@Override
49+
public boolean sourcesRequired() {
50+
return false;
51+
}
52+
53+
synchronized public void run(VirtualFile[] sources, AnalysisCallback callback, Logger log, Reporter delegate) {
54+
DelegatingReporter reporter = new DelegatingReporter(delegate);
55+
try {
56+
log.debug(this::infoOnCachedCompiler);
57+
58+
Contexts.Context initialCtx = initCtx()
59+
.fresh()
60+
.setReporter(reporter)
61+
.setSbtCallback(callback);
62+
63+
Contexts.Context context = setup(args, initialCtx)._2;
64+
65+
if(CompilerCommand.shouldStopWithInfo(context)) {
66+
throw new InterfaceCompileFailed(args, new Problem[0], StopInfoError);
67+
}
68+
69+
if (!delegate.hasErrors()) {
70+
log.debug(this::prettyPrintCompilationArguments);
71+
Compiler compiler = newCompiler(context);
72+
73+
VirtualFile[] sortedSources = new VirtualFile[sources.length];
74+
System.arraycopy(sources, 0, sortedSources, 0, sources.length);
75+
Arrays.sort(
76+
sortedSources,
77+
new Comparator<VirtualFile>() {
78+
@Override
79+
public int compare(VirtualFile x0, VirtualFile x1) {
80+
return x0.id().compareTo(x1.id());
81+
}
82+
}
83+
);
84+
85+
ListBuffer<AbstractFile> sourcesBuffer = new ListBuffer<>();
86+
for (VirtualFile file: sortedSources)
87+
sourcesBuffer.append(asDottyFile(file));
88+
doCompileFiles(compiler, sourcesBuffer.toList(), context);
89+
90+
for (xsbti.Problem problem: delegate.problems()) {
91+
callback.problem(problem.category(), problem.position(), problem.message(), problem.severity(),
92+
true);
93+
}
94+
}
95+
96+
delegate.printSummary();
97+
98+
if (delegate.hasErrors()) {
99+
log.debug(() -> "Compilation failed");
100+
throw new InterfaceCompileFailed(args, delegate.problems(), "Compilation failed");
101+
}
102+
} finally {
103+
reporter.dropDelegate();
104+
}
105+
}
106+
107+
private static AbstractFile asDottyFile(VirtualFile virtualFile) {
108+
if (virtualFile instanceof PathBasedFile)
109+
return new ZincPlainFile((PathBasedFile) virtualFile);
110+
111+
try {
112+
return new ZincVirtualFile(virtualFile);
113+
} catch (IOException e) {
114+
throw new IllegalArgumentException("invalid file " + virtualFile.name(), e);
115+
}
116+
}
117+
118+
private String infoOnCachedCompiler() {
119+
String compilerId = Integer.toHexString(hashCode());
120+
String compilerVersion = Properties.versionString();
121+
return String.format("[zinc] Running cached compiler %s for Scala Compiler %s", compilerId, compilerVersion);
122+
}
123+
124+
private String prettyPrintCompilationArguments() {
125+
StringBuilder builder = new StringBuilder();
126+
builder.append("[zinc] The Scala compiler is invoked with:");
127+
for (String opt: scalacOptions) {
128+
builder.append("\n\t");
129+
builder.append(opt);
130+
}
131+
return builder.toString();
132+
}
133+
134+
}

0 commit comments

Comments
 (0)