Skip to content

Commit

Permalink
Translate the sbt-bridge to Java.
Browse files Browse the repository at this point in the history
Only the files in the Compile configuration are translated. Tests
are kept in Scala.
  • Loading branch information
sjrd committed Jan 24, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 450a6a8 commit aab1f18
Showing 17 changed files with 610 additions and 353 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dotty.tools
package dotc
package reporting

/**
* This class mixes in a few standard traits, so that it is easier to extend from Java.
*/
abstract class AbstractReporter extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import diagnostic.messages.{ Error, ConditionalWarning }
class ConsoleReporter(
reader: BufferedReader = Console.in,
writer: PrintWriter = new PrintWriter(Console.err, true)
) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering {
) extends AbstractReporter {

import MessageContainer._

76 changes: 76 additions & 0 deletions sbt-bridge/src/xsbt/CachedCompilerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt;

import xsbti.AnalysisCallback;
import xsbti.Logger;
import xsbti.Reporter;
import xsbti.Severity;
import xsbti.compile.*;

import java.io.File;

import dotty.tools.dotc.core.Contexts.Context;
import dotty.tools.dotc.core.Contexts.ContextBase;
import dotty.tools.dotc.Main;
import dotty.tools.dotc.interfaces.*;

import java.net.URLClassLoader;

public class CachedCompilerImpl implements CachedCompiler {
private final String[] args;
private final Output output;
private final String[] outputArgs;

public CachedCompilerImpl(String[] args, Output output) {
super();
this.args = args;
this.output = output;

if (!(output instanceof SingleOutput))
throw new IllegalArgumentException("output should be a SingleOutput, was a " + output.getClass().getName());

this.outputArgs =
new String[] { "-d", ((SingleOutput) output).getOutputDirectory().getAbsolutePath().toString() };
}

public String[] commandArguments(File[] sources) {
String[] sortedSourcesAbsolute = new String[sources.length];
for (int i = 0; i < sources.length; i++)
sortedSourcesAbsolute[i] = sources[i].getAbsolutePath();
java.util.Arrays.sort(sortedSourcesAbsolute);

// Concatenate outputArgs, args and sortedSourcesAbsolute
String[] result = new String[outputArgs.length + args.length + sortedSourcesAbsolute.length];
int j = 0;
for (int i = 0; i < outputArgs.length; i++, j++)
result[j] = outputArgs[i];
for (int i = 0; i < args.length; i++, j++)
result[j] = args[i];
for (int i = 0; i < sortedSourcesAbsolute.length; i++, j++)
result[j] = sortedSourcesAbsolute[i];

return result;
}

synchronized public void run(File[] sources, DependencyChanges changes, AnalysisCallback callback, Logger log, Reporter delegate, CompileProgress progress) {
log.debug(() -> {
String msg = "Calling Dotty compiler with arguments (CompilerInterface):";
for (String arg : args)
msg = msg + "\n\t" + arg;
return msg;
});

Context ctx = new ContextBase().initialCtx().fresh()
.setSbtCallback(callback)
.setReporter(new DelegatingReporter(delegate));

URLClassLoader cl = (URLClassLoader) this.getClass().getClassLoader();

dotty.tools.dotc.reporting.Reporter reporter = Main.process(commandArguments(sources), ctx);
if (reporter.hasErrors()) {
throw new InterfaceCompileFailed(args, new Problem[0]);
}
}
}
122 changes: 122 additions & 0 deletions sbt-bridge/src/xsbt/CompilerClassLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package xsbt;

import java.lang.reflect.Field;

import java.net.URL;
import java.net.URLClassLoader;

import java.util.WeakHashMap;

/**
* A classloader to run the compiler
* <p>
* A CompilerClassLoader is constructed from a list of `urls` that need to be on
* the classpath to run the compiler and the classloader used by sbt.
* <p>
* To understand why a custom classloader is needed for the compiler, let us
* describe some alternatives that wouldn't work.
* <ul>
* <li>`new URLClassLoader(urls)`:
* The compiler contains sbt phases that callback to sbt using the `xsbti.*`
* interfaces. If `urls` does not contain the sbt interfaces we'll get a
* `ClassNotFoundException` in the compiler when we try to use them, if
* `urls` does contain the interfaces we'll get a `ClassCastException` or a
* `LinkageError` because if the same class is loaded by two different
* classloaders, they are considered distinct by the JVM.
* <li>`new URLClassLoader(urls, sbtLoader)`:
* Because of the JVM delegation model, this means that we will only load
* a class from `urls` if it's not present in the parent `sbtLoader`, but
* sbt uses its own version of the scala compiler and scala library which
* is not the one we need to run the compiler.
* </ul>
* <p>
* Our solution is to implement a subclass of URLClassLoader with no parent, instead
* we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`.
*/
public class CompilerClassLoader extends URLClassLoader {
private final ClassLoader sbtLoader;

public CompilerClassLoader(URL[] urls, ClassLoader sbtLoader) {
super(urls, null);
this.sbtLoader = sbtLoader;
}

@Override
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
if (className.startsWith("xsbti.")) {
// We can't use the loadClass overload with two arguments because it's
// protected, but we can do the same by hand (the classloader instance
// from which we call resolveClass does not matter).
Class<?> c = sbtLoader.loadClass(className);
if (resolve)
resolveClass(c);
return c;
} else {
return super.loadClass(className, resolve);
}
}

/**
* Cache the result of `fixBridgeLoader`.
* <p>
* Reusing ClassLoaders is important for warm performance since otherwise the
* JIT code cache for the compiler will be discarded between every call to
* the sbt `compile` task.
*/
private static WeakHashMap<ClassLoader, ClassLoader> fixedLoaderCache = new WeakHashMap<>();

/**
* Fix the compiler bridge ClassLoader
* <p>
* Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs
* <p>
* The classloader that we get from sbt looks like:
* <p>
* URLClassLoader(bridgeURLs,
* DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter))
* <p>
* DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and
* everything else with `scalaLoader`. Once we have loaded the dotty Main
* class using `scalaLoader`, subsequent classes in the dotty compiler will
* also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt
* compiler phases are part of dotty and still need access to the `xsbti.*`
* interfaces in `sbtLoader`, therefore DualLoader does not work for us
* (this issue is not present with scalac because the sbt phases are
* currently defined in the compiler bridge itself, not in scalac).
* <p>
* CompilerClassLoader is a replacement for DualLoader. Until we can fix
* this in sbt proper, we need to use reflection to construct our own
* fixed classloader:
* <p>
* URLClassLoader(bridgeURLs,
* CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
*
* @param bridgeLoader The classloader that sbt uses to load the compiler bridge
* @return A fixed classloader that works with dotty
*/
synchronized public static ClassLoader fixBridgeLoader(ClassLoader bridgeLoader) {
return fixedLoaderCache.computeIfAbsent(bridgeLoader, k -> computeFixedLoader(k));
}

private static ClassLoader computeFixedLoader(ClassLoader bridgeLoader) {
URLClassLoader urlBridgeLoader = (URLClassLoader) bridgeLoader;
ClassLoader dualLoader = urlBridgeLoader.getParent();
Class<?> dualLoaderClass = dualLoader.getClass();

try {
// DualLoader.parentA and DualLoader.parentB are private
Field parentAField = dualLoaderClass.getDeclaredField("parentA");
parentAField.setAccessible(true);
Field parentBField = dualLoaderClass.getDeclaredField("parentB");
parentBField.setAccessible(true);
URLClassLoader scalaLoader = (URLClassLoader) parentAField.get(dualLoader);
URLClassLoader sbtLoader = (URLClassLoader) parentBField.get(dualLoader);

URL[] bridgeURLs = urlBridgeLoader.getURLs();
return new URLClassLoader(bridgeURLs,
new CompilerClassLoader(scalaLoader.getURLs(), sbtLoader));
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
104 changes: 0 additions & 104 deletions sbt-bridge/src/xsbt/CompilerClassLoader.scala

This file was deleted.

43 changes: 43 additions & 0 deletions sbt-bridge/src/xsbt/CompilerInterface.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt;

import xsbti.AnalysisCallback;
import xsbti.Logger;
import xsbti.Reporter;
import xsbti.Severity;
import xsbti.compile.*;

import java.io.File;

import dotty.tools.dotc.core.Contexts.ContextBase;
import dotty.tools.dotc.Main;
import dotty.tools.dotc.interfaces.*;

import java.lang.reflect.InvocationTargetException;
import java.net.URLClassLoader;

public final class CompilerInterface {
public CachedCompiler newCompiler(String[] options, Output output, Logger initialLog, Reporter initialDelegate) {
// The classloader that sbt uses to load the compiler bridge is broken
// (see CompilerClassLoader#fixBridgeLoader for details). To workaround
// this we construct our own ClassLoader and then run the following code
// with it:
// new CachedCompilerImpl(options, output)

try {
ClassLoader bridgeLoader = this.getClass().getClassLoader();
ClassLoader fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader);
Class<?> cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl");
return (CachedCompiler) cciClass.getConstructors()[0].newInstance(options, output);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}

public void run(File[] sources, DependencyChanges changes, AnalysisCallback callback, Logger log,
Reporter delegate, CompileProgress progress, CachedCompiler cached) {
cached.run(sources, changes, callback, log, delegate, progress);
}
}
Loading
Oops, something went wrong.

0 comments on commit aab1f18

Please sign in to comment.