Skip to content

Commit f6cea5f

Browse files
sjrdsmarter
authored andcommitted
Translate the sbt-bridge to Java.
Only the files in the Compile configuration are translated. Tests are kept in Scala.
1 parent 2d16d9f commit f6cea5f

17 files changed

+602
-353
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dotty.tools
2+
package dotc
3+
package reporting
4+
5+
/**
6+
* This class mixes in a few standard traits, so that it is easier to extend from Java.
7+
*/
8+
abstract class AbstractReporter extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering

compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import diagnostic.messages.{ Error, ConditionalWarning }
1313
class ConsoleReporter(
1414
reader: BufferedReader = Console.in,
1515
writer: PrintWriter = new PrintWriter(Console.err, true)
16-
) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering {
16+
) extends AbstractReporter {
1717

1818
import MessageContainer._
1919

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* sbt -- Simple Build Tool
2+
* Copyright 2008, 2009 Mark Harrah
3+
*/
4+
package xsbt;
5+
6+
import xsbti.AnalysisCallback;
7+
import xsbti.Logger;
8+
import xsbti.Reporter;
9+
import xsbti.Severity;
10+
import xsbti.compile.*;
11+
12+
import java.io.File;
13+
14+
import dotty.tools.dotc.core.Contexts.Context;
15+
import dotty.tools.dotc.core.Contexts.ContextBase;
16+
import dotty.tools.dotc.Main;
17+
import dotty.tools.dotc.interfaces.*;
18+
19+
import java.net.URLClassLoader;
20+
21+
public class CachedCompilerImpl implements CachedCompiler {
22+
private final String[] args;
23+
private final Output output;
24+
private final String[] outputArgs;
25+
26+
public CachedCompilerImpl(String[] args, Output output) {
27+
super();
28+
this.args = args;
29+
this.output = output;
30+
31+
if (!(output instanceof SingleOutput))
32+
throw new IllegalArgumentException("output should be a SingleOutput, was a " + output.getClass().getName());
33+
34+
this.outputArgs =
35+
new String[] { "-d", ((SingleOutput) output).getOutputDirectory().getAbsolutePath().toString() };
36+
}
37+
38+
public String[] commandArguments(File[] sources) {
39+
String[] sortedSourcesAbsolute = new String[sources.length];
40+
for (int i = 0; i < sources.length; i++)
41+
sortedSourcesAbsolute[i] = sources[i].getAbsolutePath();
42+
java.util.Arrays.sort(sortedSourcesAbsolute);
43+
44+
// Concatenate outputArgs, args and sortedSourcesAbsolute
45+
String[] result = new String[outputArgs.length + args.length + sortedSourcesAbsolute.length];
46+
int j = 0;
47+
for (int i = 0; i < outputArgs.length; i++, j++)
48+
result[j] = outputArgs[i];
49+
for (int i = 0; i < args.length; i++, j++)
50+
result[j] = args[i];
51+
for (int i = 0; i < sortedSourcesAbsolute.length; i++, j++)
52+
result[j] = sortedSourcesAbsolute[i];
53+
54+
return result;
55+
}
56+
57+
synchronized public void run(File[] sources, DependencyChanges changes, AnalysisCallback callback, Logger log, Reporter delegate, CompileProgress progress) {
58+
log.debug(() -> {
59+
String msg = "Calling Dotty compiler with arguments (CompilerInterface):";
60+
for (String arg : args)
61+
msg = msg + "\n\t" + arg;
62+
return msg;
63+
});
64+
65+
Context ctx = new ContextBase().initialCtx().fresh()
66+
.setSbtCallback(callback)
67+
.setReporter(new DelegatingReporter(delegate));
68+
69+
URLClassLoader cl = (URLClassLoader) this.getClass().getClassLoader();
70+
71+
dotty.tools.dotc.reporting.Reporter reporter = Main.process(commandArguments(sources), ctx);
72+
if (reporter.hasErrors()) {
73+
throw new InterfaceCompileFailed(args, new Problem[0]);
74+
}
75+
}
76+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package xsbt;
2+
3+
import java.lang.reflect.Field;
4+
5+
import java.net.URL;
6+
import java.net.URLClassLoader;
7+
8+
import java.util.WeakHashMap;
9+
10+
/**
11+
* A classloader to run the compiler
12+
* <p>
13+
* A CompilerClassLoader is constructed from a list of `urls` that need to be on
14+
* the classpath to run the compiler and the classloader used by sbt.
15+
* <p>
16+
* To understand why a custom classloader is needed for the compiler, let us
17+
* describe some alternatives that wouldn't work.
18+
* <ul>
19+
* <li>`new URLClassLoader(urls)`:
20+
* The compiler contains sbt phases that callback to sbt using the `xsbti.*`
21+
* interfaces. If `urls` does not contain the sbt interfaces we'll get a
22+
* `ClassNotFoundException` in the compiler when we try to use them, if
23+
* `urls` does contain the interfaces we'll get a `ClassCastException` or a
24+
* `LinkageError` because if the same class is loaded by two different
25+
* classloaders, they are considered distinct by the JVM.
26+
* <li>`new URLClassLoader(urls, sbtLoader)`:
27+
* Because of the JVM delegation model, this means that we will only load
28+
* a class from `urls` if it's not present in the parent `sbtLoader`, but
29+
* sbt uses its own version of the scala compiler and scala library which
30+
* is not the one we need to run the compiler.
31+
* </ul>
32+
* <p>
33+
* Our solution is to implement a subclass of URLClassLoader with no parent, instead
34+
* we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`.
35+
*/
36+
public class CompilerClassLoader extends URLClassLoader {
37+
private final ClassLoader sbtLoader;
38+
39+
public CompilerClassLoader(URL[] urls, ClassLoader sbtLoader) {
40+
super(urls, null);
41+
this.sbtLoader = sbtLoader;
42+
}
43+
44+
@Override
45+
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
46+
if (className.startsWith("xsbti.")) {
47+
// We can't use the loadClass overload with two arguments because it's
48+
// protected, but we can do the same by hand (the classloader instance
49+
// from which we call resolveClass does not matter).
50+
Class<?> c = sbtLoader.loadClass(className);
51+
if (resolve)
52+
resolveClass(c);
53+
return c;
54+
} else {
55+
return super.loadClass(className, resolve);
56+
}
57+
}
58+
59+
/**
60+
* Cache the result of `fixBridgeLoader`.
61+
* <p>
62+
* Reusing ClassLoaders is important for warm performance since otherwise the
63+
* JIT code cache for the compiler will be discarded between every call to
64+
* the sbt `compile` task.
65+
*/
66+
private static WeakHashMap<ClassLoader, ClassLoader> fixedLoaderCache = new WeakHashMap<>();
67+
68+
/**
69+
* Fix the compiler bridge ClassLoader
70+
* <p>
71+
* Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs
72+
* <p>
73+
* The classloader that we get from sbt looks like:
74+
* <p>
75+
* URLClassLoader(bridgeURLs,
76+
* DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter))
77+
* <p>
78+
* DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and
79+
* everything else with `scalaLoader`. Once we have loaded the dotty Main
80+
* class using `scalaLoader`, subsequent classes in the dotty compiler will
81+
* also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt
82+
* compiler phases are part of dotty and still need access to the `xsbti.*`
83+
* interfaces in `sbtLoader`, therefore DualLoader does not work for us
84+
* (this issue is not present with scalac because the sbt phases are
85+
* currently defined in the compiler bridge itself, not in scalac).
86+
* <p>
87+
* CompilerClassLoader is a replacement for DualLoader. Until we can fix
88+
* this in sbt proper, we need to use reflection to construct our own
89+
* fixed classloader:
90+
* <p>
91+
* URLClassLoader(bridgeURLs,
92+
* CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
93+
*
94+
* @param bridgeLoader The classloader that sbt uses to load the compiler bridge
95+
* @return A fixed classloader that works with dotty
96+
*/
97+
synchronized public static ClassLoader fixBridgeLoader(ClassLoader bridgeLoader) {
98+
return fixedLoaderCache.computeIfAbsent(bridgeLoader, k -> computeFixedLoader(k));
99+
}
100+
101+
private static ClassLoader computeFixedLoader(ClassLoader bridgeLoader) {
102+
URLClassLoader urlBridgeLoader = (URLClassLoader) bridgeLoader;
103+
ClassLoader dualLoader = urlBridgeLoader.getParent();
104+
Class<?> dualLoaderClass = dualLoader.getClass();
105+
106+
try {
107+
// DualLoader.parentA and DualLoader.parentB are private
108+
Field parentAField = dualLoaderClass.getDeclaredField("parentA");
109+
parentAField.setAccessible(true);
110+
Field parentBField = dualLoaderClass.getDeclaredField("parentB");
111+
parentBField.setAccessible(true);
112+
URLClassLoader scalaLoader = (URLClassLoader) parentAField.get(dualLoader);
113+
URLClassLoader sbtLoader = (URLClassLoader) parentBField.get(dualLoader);
114+
115+
URL[] bridgeURLs = urlBridgeLoader.getURLs();
116+
return new URLClassLoader(bridgeURLs,
117+
new CompilerClassLoader(scalaLoader.getURLs(), sbtLoader));
118+
} catch (NoSuchFieldException | IllegalAccessException e) {
119+
throw new RuntimeException(e);
120+
}
121+
}
122+
}

sbt-bridge/src/xsbt/CompilerClassLoader.scala

Lines changed: 0 additions & 104 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* sbt -- Simple Build Tool
2+
* Copyright 2008, 2009 Mark Harrah
3+
*/
4+
package xsbt;
5+
6+
import xsbti.AnalysisCallback;
7+
import xsbti.Logger;
8+
import xsbti.Reporter;
9+
import xsbti.Severity;
10+
import xsbti.compile.*;
11+
12+
import java.io.File;
13+
14+
import dotty.tools.dotc.core.Contexts.ContextBase;
15+
import dotty.tools.dotc.Main;
16+
import dotty.tools.dotc.interfaces.*;
17+
18+
import java.lang.reflect.InvocationTargetException;
19+
import java.net.URLClassLoader;
20+
21+
public final class CompilerInterface {
22+
public CachedCompiler newCompiler(String[] options, Output output, Logger initialLog, Reporter initialDelegate) {
23+
// The classloader that sbt uses to load the compiler bridge is broken
24+
// (see CompilerClassLoader#fixBridgeLoader for details). To workaround
25+
// this we construct our own ClassLoader and then run the following code
26+
// with it:
27+
// new CachedCompilerImpl(options, output)
28+
29+
try {
30+
ClassLoader bridgeLoader = this.getClass().getClassLoader();
31+
ClassLoader fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader);
32+
Class<?> cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl");
33+
return (CachedCompiler) cciClass.getConstructors()[0].newInstance(options, output);
34+
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
35+
throw new RuntimeException(e);
36+
}
37+
}
38+
39+
public void run(File[] sources, DependencyChanges changes, AnalysisCallback callback, Logger log,
40+
Reporter delegate, CompileProgress progress, CachedCompiler cached) {
41+
cached.run(sources, changes, callback, log, delegate, progress);
42+
}
43+
}

0 commit comments

Comments
 (0)