Skip to content

Commit 3363f00

Browse files
committed
Add a dotty-interfaces package
We introduce a new entry point for the compiler in `dotty.tools.dotc.Driver`: ``` def process(args: Array[String], simple: interfaces.SimpleReporter, callback: interfaces.CompilerCallback): interfaces.ReporterResult ``` Except for `args` which is just an array, the argument types and return type of this method are Java interfaces defined in a new package called `dotty-interfaces` which has a stable ABI. This means that you can programmatically run a compiler with a custom reporter and callbacks without having to recompile it against every version of dotty: you only need to have `dotty-interfaces` present at compile-time and call the `process` method using Java reflection. See `test/test/InterfaceEntryPointTest.scala` for a concrete example. This design is based on discussions with the IntelliJ IDEA Scala plugin team. Thanks to Nikolay Tropin for the discussions and his PR proposal (see #1011).
1 parent 94b41d5 commit 3363f00

File tree

17 files changed

+361
-67
lines changed

17 files changed

+361
-67
lines changed

bin/dotc

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ ReplMain=test.DottyRepl
4242

4343

4444

45-
# autodetecting the compiler jar. this is location where sbt 'packages' it
45+
# autodetecting the compiler jars. this is the location where sbt 'packages' them
46+
INTERFACES_JAR=$DOTTY_ROOT/interfaces/target/dotty-interfaces-$DOTTY_VERSION.jar
4647
MAIN_JAR=$DOTTY_ROOT/target/scala-$SCALA_BINARY_VERSION/dotty_$SCALA_BINARY_VERSION-$DOTTY_VERSION.jar
4748
TEST_JAR=$DOTTY_ROOT/target/scala-$SCALA_BINARY_VERSION/dotty_$SCALA_BINARY_VERSION-$DOTTY_VERSION-tests.jar
4849
DOTTY_JAR=$DOTTY_ROOT/dotty.jar
@@ -62,7 +63,7 @@ function checkjar {
6263
echo "The required jar file was built successfully."
6364
fi
6465
else
65-
NEW_FILES="$(find "$DOTTY_ROOT/$3" -iname "*.scala" -newer "$1")"
66+
NEW_FILES="$(find "$DOTTY_ROOT/$3" \( -iname "*.scala" -o -iname "*.java" \) -newer "$1")"
6667
if [ ! -z "$NEW_FILES" ];
6768
then
6869
echo "new files detected. rebuilding"
@@ -74,6 +75,7 @@ function checkjar {
7475
fi
7576
}
7677

78+
checkjar $INTERFACES_JAR interfaces/package interfaces
7779
checkjar $MAIN_JAR package src
7880
checkjar $TEST_JAR test:package test
7981

@@ -198,6 +200,8 @@ classpathArgs () {
198200
else
199201
toolchain="$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR"
200202
fi
203+
bcpJars="$INTERFACES_JAR:$MAIN_JAR"
204+
cpJars="$INTERFACES_JAR:$MAIN_JAR:$TEST_JAR"
201205

202206
if [[ -n "$cygwin" ]]; then
203207
if [[ "$OS" = "Windows_NT" ]] && cygpath -m .>/dev/null 2>/dev/null ; then
@@ -207,18 +211,18 @@ classpathArgs () {
207211
fi
208212

209213
if [[ -n $bootcp ]]; then
210-
boot_classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR")"
211-
classpath="$(cygpath --path --$format "$MAIN_JAR:$TEST_JAR")"
214+
boot_classpath="$(cygpath --path --$format "$toolchain:$bcpJars")"
215+
classpath="$(cygpath --path --$format "$cpJars")"
212216
cpArgs="-Xbootclasspath/a:$boot_classpath -classpath $classpath"
213217
else
214-
classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR:$TEST_JAR")"
218+
classpath="$(cygpath --path --$format "$toolchain:$cpJars")"
215219
cpArgs="-classpath $classpath"
216220
fi
217221
else
218222
if [[ -n $bootcp ]]; then
219-
cpArgs="-Xbootclasspath/a:$toolchain:$MAIN_JAR -classpath $MAIN_JAR:$TEST_JAR"
223+
cpArgs="-Xbootclasspath/a:$toolchain:$bcpJars -classpath $cpJars"
220224
else
221-
cpArgs="-classpath $toolchain:$MAIN_JAR:$TEST_JAR"
225+
cpArgs="-classpath $toolchain:$cpJars"
222226
fi
223227
fi
224228
echo ${cpArgs}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
import java.io.File;
4+
5+
/** Set of callbacks called in response to events during the compilation process.
6+
*
7+
* You should implement this interface if you want to react to one or more of
8+
* these events.
9+
*
10+
* @see the method `process` of `dotty.tools.dotc.Driver` for more information.
11+
*/
12+
public interface CompilerCallback {
13+
/** Called when a class has been generated.
14+
*
15+
* @param source The source file corresponding to this class.
16+
* Example: ./src/library/scala/collection/Seq.scala
17+
* @param generatedClass The generated classfile for this class.
18+
* Example: ./scala/collection/Seq$.class
19+
* @param className The name of this class.
20+
* Example: scala.collection.Seq$
21+
*/
22+
default void onClassGenerated(File source, File generatedClass, String className) {};
23+
24+
/** Called when every class for this file has been generated.
25+
*
26+
* @param source The source file.
27+
* Example: ./src/library/scala/collection/Seq.scala
28+
*/
29+
default void onSourceCompiled(File source) {};
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
import java.util.Optional;
4+
5+
/** A diagnostic is a message emitted during the compilation process.
6+
*
7+
* It can either be an error, a warning or an information.
8+
*
9+
* User code should not implement this interface, but it may have to
10+
* manipulate objects of this type.
11+
*/
12+
public interface Diagnostic {
13+
public static final int ERROR = 2;
14+
public static final int WARNING = 1;
15+
public static final int INFO = 0;
16+
17+
/** The message to report */
18+
String message();
19+
20+
/** Level of the diagnostic, can be either ERROR, WARNING or INFO */
21+
int level();
22+
23+
/** The position in a source file of the code that caused this diagnostic
24+
* to be emitted. */
25+
Optional<SourcePosition> position();
26+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
/** Summary of the diagnostics emitted by a Reporter.
4+
*
5+
* User code should not implement this interface, but it may have to
6+
* manipulate objects of this type.
7+
*/
8+
public interface ReporterResult {
9+
/** Have we emitted any error ? */
10+
boolean hasErrors();
11+
/** Number of errors that have been emitted */
12+
int errorCount();
13+
14+
/** Have we emitted any warning ? */
15+
boolean hasWarnings();
16+
/** Number of warnings that have been emitted */
17+
int warningCount();
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
/** Report errors, warnings and info messages during the compilation process
4+
*
5+
* You should implement this interface if you want to handle the diagnostics
6+
* returned by the compiler yourself.
7+
*
8+
* @see the method `process` of `dotty.tools.dotc.Driver` for more information.
9+
*/
10+
public interface SimpleReporter {
11+
/** Report a diagnostic. */
12+
void report(Diagnostic diag);
13+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
import java.io.File;
4+
5+
/** A source file that may correspond to a file on disk but may also be virtual.
6+
*
7+
* User code should not implement this interface, but it may have to
8+
* manipulate objects of this type.
9+
*/
10+
public interface SourceFile {
11+
/** The name of this file, note that two files may have the same name. */
12+
String name();
13+
14+
/** The path of this file, this might be a virtual path of an unspecified format. */
15+
String path();
16+
17+
/** The content of this file as seen by the compiler. */
18+
char[] content();
19+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
/** A position in a source file.
4+
*
5+
* A position is a range between a start offset and an end offset, as well as a
6+
* point inside this range.
7+
*
8+
* As a convenience, we also provide methods that return the line and the column
9+
* corresponding to each offset.
10+
*
11+
* User code should not implement this interface, but it may have to
12+
* manipulate objects of this type.
13+
*/
14+
public interface SourcePosition {
15+
/** Content of the line which contains the point */
16+
String lineContent();
17+
18+
/** Offset to the point */
19+
int point();
20+
/** Line number of the point, starting at 0 */
21+
int line();
22+
/** Column number of the point, starting at 0 */
23+
int column();
24+
25+
/** Offset to the range start */
26+
int start();
27+
/** Line number of the range start, starting at 0 */
28+
int startLine();
29+
/** Column number of the range start, starting at 0 */
30+
int startColumn();
31+
32+
/** Offset to the range end */
33+
int end();
34+
/** Line number of the range end, starting at 0 */
35+
int endLine();
36+
/** Column number of the range end, starting at 0 */
37+
int endColumn();
38+
39+
/** The source file corresponding to this position.
40+
* The values returned by `point()`, `start()` and `end()`
41+
* are indices in the array returned by `source().content()`.
42+
*/
43+
SourceFile source();
44+
}

project/Build.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,16 @@ object DottyBuild extends Build {
3535
)
3636
}
3737

38+
lazy val `dotty-interfaces` = project.in(file("interfaces")).
39+
settings(
40+
// Do not append Scala versions to the generated artifacts
41+
crossPaths := false,
42+
// Do not depend on the Scala library
43+
autoScalaLibrary := false
44+
)
45+
3846
lazy val dotty = project.in(file(".")).
47+
dependsOn(`dotty-interfaces`).
3948
settings(
4049
// set sources to src/, tests to test/ and resources to resources/
4150
scalaSource in Compile := baseDirectory.value / "src",
@@ -105,7 +114,7 @@ object DottyBuild extends Build {
105114
parallelExecution in Test := false,
106115

107116
// http://grokbase.com/t/gg/simple-build-tool/135ke5y90p/sbt-setting-jvm-boot-paramaters-for-scala
108-
javaOptions <++= (managedClasspath in Runtime, packageBin in Compile) map { (attList, bin) =>
117+
javaOptions <++= (dependencyClasspath in Runtime, packageBin in Compile) map { (attList, bin) =>
109118
// put the Scala {library, reflect} in the classpath
110119
val path = for {
111120
file <- attList.map(_.data)

src/dotty/tools/dotc/CompilerCallback.scala

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/dotty/tools/dotc/Driver.scala

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import util.DotClass
77
import reporting._
88
import scala.util.control.NonFatal
99

10+
/** Run the Dotty compiler.
11+
*
12+
* Extending this class lets you customize many aspect of the compilation
13+
* process, but in most cases you only need to call [[process]] on the
14+
* existing object [[Main]].
15+
*/
1016
abstract class Driver extends DotClass {
1117

1218
val prompt = "\ndotc> "
@@ -41,12 +47,35 @@ abstract class Driver extends DotClass {
4147
(fileNames, ctx)
4248
}
4349

50+
/** Entry point to the compiler that can be conveniently used with Java reflection.
51+
*
52+
* This entry point can easily be used without depending on the `dotty` package,
53+
* you only need to depend on `dotty-interfaces` and call this method using
54+
* reflection. This allows you to write code that will work against multiple
55+
* versions of dotty without recompilation.
56+
*
57+
* The trade-off is that you can only pass a SimpleReporter to this method
58+
* and not a normal Reporter which is more powerful.
59+
*
60+
* Usage example: [[https://github.com/lampepfl/dotty/tree/master/test/test/InterfaceEntryPointTest.scala]]
61+
*
62+
* @param args Arguments to pass to the compiler.
63+
* @param simple Used to log errors, warnings, and info messages.
64+
* The default reporter is used if this is `null`.
65+
* @param callback Used to execute custom code during the compilation
66+
* process. No callbacks will be executed if this is `null`.
67+
* @return
68+
*/
69+
final def process(args: Array[String], simple: interfaces.SimpleReporter,
70+
callback: interfaces.CompilerCallback): interfaces.ReporterResult = {
71+
val reporter = if (simple == null) null else Reporter.fromSimpleReporter(simple)
72+
process(args, reporter, callback)
73+
}
4474

4575
/** Principal entry point to the compiler.
46-
* Creates a new compiler instance and run it with arguments `args`.
4776
*
48-
* The optional arguments of this method all have `null` as their default
49-
* value, this makes it easier to call this method by reflection or from Java.
77+
* Usage example: [[https://github.com/lampepfl/dotty/tree/master/test/test/OtherEntryPointsTest.scala]]
78+
* in method `runCompiler`
5079
*
5180
* @param args Arguments to pass to the compiler.
5281
* @param reporter Used to log errors, warnings, and info messages.
@@ -57,7 +86,7 @@ abstract class Driver extends DotClass {
5786
* if compilation succeeded.
5887
*/
5988
final def process(args: Array[String], reporter: Reporter = null,
60-
callback: CompilerCallback = null): Reporter = {
89+
callback: interfaces.CompilerCallback = null): Reporter = {
6190
val ctx = initCtx.fresh
6291
if (reporter != null)
6392
ctx.setReporter(reporter)
@@ -75,7 +104,7 @@ abstract class Driver extends DotClass {
75104
* with sbt.
76105
*/
77106
final def process(args: Array[String]): Reporter =
78-
process(args, null, null)
107+
process(args, null: Reporter, null: interfaces.CompilerCallback)
79108

80109
/** Entry point to the compiler using a custom `Context`.
81110
*
@@ -84,6 +113,9 @@ abstract class Driver extends DotClass {
84113
* the other overloads cannot be overriden, instead you
85114
* should override this one which they call internally.
86115
*
116+
* Usage example: [[https://github.com/lampepfl/dotty/tree/master/test/test/OtherEntryPointsTest.scala]]
117+
* in method `runCompilerWithContext`
118+
*
87119
* @param args Arguments to pass to the compiler.
88120
* @param rootCtx The root Context to use.
89121
* @return The `Reporter` used. Use `Reporter#hasErrors` to check

src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dotty.tools
22
package dotc
33
package core
44

5+
import interfaces.CompilerCallback
56
import Decorators._
67
import Periods._
78
import Names._

0 commit comments

Comments
 (0)