Fix reboot full in RunFromSourceMain

The initial implementation of reboot full did not work correctly. After
this change, the project/extra scripted test, which exercises reboot
full works.
This commit is contained in:
Ethan Atkins 2020-01-17 17:23:30 -08:00
parent c47db91bdc
commit eb66651886
2 changed files with 218 additions and 180 deletions

View File

@ -7,12 +7,13 @@
package sbt
import java.io.File.pathSeparator
import sbt.internal.scriptedtest.ScriptedLauncher
import sbt.util.LogExchange
import scala.annotation.tailrec
import scala.sys.process.Process
import java.io.File.pathSeparator
object RunFromSourceMain {
def fork(
@ -76,14 +77,16 @@ object RunFromSourceMain {
sbtVersion: String,
classpath: String,
args: Seq[String],
): Option[(File, Seq[String])] =
try launch(baseDir, scalaVersion, sbtVersion, classpath, args) map exit
): Option[(File, Seq[String])] = {
try launch(defaultBootDirectory, baseDir, scalaVersion, sbtVersion, classpath, args) map exit
catch {
case r: xsbti.FullReload => Some((baseDir, r.arguments()))
case scala.util.control.NonFatal(e) => e.printStackTrace(); errorAndExit(e.toString)
}
}
private def launch(
bootDirectory: File,
baseDirectory: File,
scalaVersion: String,
sbtVersion: String,
@ -92,7 +95,7 @@ object RunFromSourceMain {
): Option[Int] = {
ScriptedLauncher
.launch(
scalaHome(scalaVersion),
scalaHome(bootDirectory, scalaVersion),
sbtVersion,
scalaVersion,
bootDirectory,
@ -107,14 +110,15 @@ object RunFromSourceMain {
}
}
private lazy val bootDirectory: File = file(sys.props("user.home")) / ".sbt" / "boot"
private def scalaHome(scalaVersion: String): File = {
private lazy val defaultBootDirectory: File =
file(sys.props("user.home")) / ".sbt" / "scripted" / "boot"
private def scalaHome(bootDirectory: File, scalaVersion: String): File = {
val log = sbt.util.LogExchange.logger("run-from-source")
val scalaHome0 = bootDirectory / s"scala-$scalaVersion"
if ((scalaHome0 / "lib").exists) scalaHome0
else {
log.info(s"""scalaHome ($scalaHome0) wasn't found""")
val fakeboot = file(sys.props("user.home")) / ".sbt" / "fakeboot"
val fakeboot = bootDirectory / "fakeboot"
val scalaHome1 = fakeboot / s"scala-$scalaVersion"
val scalaHome1Lib = scalaHome1 / "lib"
val scalaHome1Temp = scalaHome1 / "temp"

View File

@ -15,26 +15,25 @@ import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import xsbti.AppConfiguration;
import xsbti.AppMain;
import xsbti.AppProvider;
import xsbti.ApplicationID;
import xsbti.ComponentProvider;
import xsbti.Continue;
import xsbti.CrossValue;
import xsbti.Exit;
import xsbti.FullReload;
import xsbti.GlobalLock;
import xsbti.Launcher;
import xsbti.MainResult;
import xsbti.Predefined;
import xsbti.PredefinedRepository;
import xsbti.Reboot;
import xsbti.Repository;
import xsbti.ScalaProvider;
@ -54,26 +53,27 @@ public class ScriptedLauncher {
final File bootDirectory,
final File baseDir,
final File[] classpath,
String[] args)
throws MalformedURLException, InvocationTargetException, ClassNotFoundException,
NoSuchMethodException, IllegalAccessException {
String[] arguments)
throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, IOException {
String[] args = arguments;
Object appID = null;
if (System.getProperty("sbt.launch.jar") == null) {
while (true) {
final URL configURL = URLForClass(xsbti.AppConfiguration.class);
final URL mainURL = URLForClass(sbt.xMain.class);
final URL scriptedURL = URLForClass(ScriptedLauncher.class);
final ClassLoader topLoader = new URLClassLoader(new URL[] {configURL}, top());
final URLClassLoader loader =
new URLClassLoader(new URL[] {mainURL, scriptedURL}, topLoader);
final ClassLoader previous = Thread.currentThread().getContextClassLoader();
try {
final AtomicInteger result = new AtomicInteger(-1);
final AtomicReference<String[]> newArguments = new AtomicReference<>();
final ClassLoader previous = Thread.currentThread().getContextClassLoader();
final URL configURL = URLForClass(xsbti.AppConfiguration.class);
final URL mainURL = URLForClass(sbt.xMain.class);
final URL scriptedURL = URLForClass(ScriptedLauncher.class);
final ClassLoader topLoader = new URLClassLoader(new URL[] {configURL}, top());
final URLClassLoader loader = new URLClassLoader(new URL[] {mainURL, scriptedURL}, topLoader);
try {
while (true) {
final Class<?> clazz = loader.loadClass("sbt.internal.scriptedtest.ScriptedLauncher");
final Class<?> reboot = loader.loadClass("xsbti.Reboot");
final Class<?> exit = loader.loadClass("xsbti.Exit");
Method method =
clazz.getDeclaredMethod(
"launchImpl",
ClassLoader.class,
"getConf",
ClassLoader.class,
File.class,
String.class,
@ -82,30 +82,56 @@ public class ScriptedLauncher {
File.class,
File[].class,
String[].class,
AtomicInteger.class,
AtomicReference.class);
method.invoke(
null,
topLoader,
loader,
scalaHome,
sbtVersion,
scalaVersion,
bootDirectory,
baseDir,
classpath,
args,
result,
newArguments);
final int res = result.get();
if (res >= 0) return res == Integer.MAX_VALUE ? Optional.empty() : Optional.of(res);
else args = newArguments.get();
} catch (final InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) throw (RuntimeException) e.getCause();
else throw e;
} finally {
swap(loader, previous);
loader.loadClass("xsbti.ApplicationID"));
Thread.currentThread().setContextClassLoader(loader);
try {
final Object conf =
method.invoke(
null,
topLoader,
scalaHome,
sbtVersion,
scalaVersion,
bootDirectory,
baseDir,
classpath,
args,
appID);
final Object launchResult =
clazz
.getDeclaredMethod(
"launchImpl", ClassLoader.class, ClassLoader.class, Object.class)
.invoke(null, topLoader, loader, conf);
if (reboot.isAssignableFrom(launchResult.getClass())) {
final Object a = reboot.getDeclaredMethod("arguments").invoke(launchResult);
final int length = Array.getLength(a);
args = new String[length];
for (int j = 0; j < length; ++j) {
args[j] = (String) Array.get(a, j);
}
appID = reboot.getDeclaredMethod("app").invoke(launchResult);
} else if (exit.isAssignableFrom(launchResult.getClass())) {
return Optional.of((Integer) exit.getDeclaredMethod("code").invoke(launchResult));
}
} catch (final InvocationTargetException e) {
Throwable t = e.getCause();
while (t != null && !t.getClass().getCanonicalName().equals("xsbti.FullReload"))
t = t.getCause();
final RuntimeException reload = t == null ? null : (RuntimeException) t;
if (reload != null) {
final boolean clean =
(boolean) reload.getClass().getDeclaredMethod("clean").invoke(reload);
if (clean) deleteRecursive(bootDirectory);
final Object reloadArgs =
reload.getClass().getDeclaredMethod("arguments").invoke(reload);
throw new xsbti.FullReload((String[]) reloadArgs, true);
}
if (e.getCause() instanceof RuntimeException) throw (RuntimeException) e.getCause();
throw new RuntimeException(e.getCause());
}
}
} finally {
swap(loader, previous);
}
} else {
final URL url = new URL("file:" + System.getProperty("sbt.launch.jar"));
@ -141,72 +167,31 @@ public class ScriptedLauncher {
Thread.currentThread().setContextClassLoader(stashed);
}
private static void copy(final File[] files, final File toDirectory) {
private static boolean copy(final File[] files, final File toDirectory) throws IOException {
boolean result = true;
for (final File file : files) {
try {
Files.createDirectories(toDirectory.toPath());
Files.copy(file.toPath(), toDirectory.toPath().resolve(file.getName()));
} catch (final IOException e) {
e.printStackTrace(System.err);
} catch (final FileAlreadyExistsException e) {
result = false;
}
}
return result;
}
@SuppressWarnings("unused")
public static void launchImpl(
final ClassLoader topLoader,
final ClassLoader loader,
final File scalaHome,
final String sbtVersion,
final String scalaVersion,
final File bootDirectory,
final File baseDir,
final File[] classpath,
final String[] args,
final AtomicInteger result,
final AtomicReference<String[]> newArguments)
public static Object launchImpl(
final ClassLoader topLoader, final ClassLoader loader, final Object conf)
throws ClassNotFoundException, InvocationTargetException, IllegalAccessException,
NoSuchMethodException, InstantiationException {
final AppConfiguration conf =
getConf(
topLoader,
scalaHome,
sbtVersion,
scalaVersion,
bootDirectory,
baseDir,
classpath,
args);
final Class<?> clazz = loader.loadClass("sbt.xMain");
final Object instance = clazz.getConstructor().newInstance();
final Method run = clazz.getDeclaredMethod("run", loader.loadClass("xsbti.AppConfiguration"));
Object runResult;
try {
runResult = run.invoke(instance, conf);
} catch (final InvocationTargetException e) {
runResult = e.getCause();
}
if (runResult instanceof Reboot) newArguments.set(((Reboot) runResult).arguments());
else if (runResult instanceof FullReload)
newArguments.set(((FullReload) runResult).arguments());
else if (runResult instanceof Exit) {
result.set(((Exit) runResult).code());
} else if (runResult instanceof Continue) {
result.set(Integer.MAX_VALUE);
} else if (runResult instanceof Throwable) {
((Throwable) runResult).printStackTrace(System.err);
result.set(1);
} else {
handleUnknownMainResult((MainResult) runResult);
}
}
private static void handleUnknownMainResult(MainResult x) {
final String clazz = x == null ? "" : " (class: " + x.getClass() + ")";
System.err.println("Invalid main result: " + x + clazz);
System.exit(1);
return run.invoke(instance, conf);
}
@SuppressWarnings("unused")
public static AppConfiguration getConf(
final ClassLoader topLoader,
final File scalaHome,
@ -215,54 +200,111 @@ public class ScriptedLauncher {
final File bootDirectory,
final File baseDir,
final File[] classpath,
String[] args) {
String[] args,
final ApplicationID appID) {
final File libDir = new File(scalaHome, "lib");
final AtomicReference<File[]> classpathExtra = new AtomicReference<>(new File[0]);
final ApplicationID id =
new ApplicationID() {
@Override
public String groupID() {
return "org.scala-sbt";
}
appID != null
? appID
: new ApplicationID() {
@Override
public String groupID() {
return "org.scala-sbt";
}
@Override
public String name() {
return "sbt";
}
@Override
public String name() {
return "sbt";
}
@Override
public String version() {
return sbtVersion;
}
@Override
public String version() {
return sbtVersion;
}
@Override
public String mainClass() {
return "sbt.xMain";
}
@Override
public String mainClass() {
return "sbt.xMain";
}
@Override
public String[] mainComponents() {
return new String[] {"xsbti", "extra"};
}
@Override
public String[] mainComponents() {
return new String[] {"xsbti", "extra"};
}
@Deprecated
@Override
public boolean crossVersioned() {
return false;
}
@Deprecated
@Override
public boolean crossVersioned() {
return false;
}
@Override
public CrossValue crossVersionedValue() {
return CrossValue.Disabled;
}
@Override
public CrossValue crossVersionedValue() {
return CrossValue.Disabled;
}
@Override
public File[] classpathExtra() {
return new File[0];
}
};
@Override
public File[] classpathExtra() {
return classpathExtra.get();
}
};
final File appHome =
scalaHome.toPath().resolve(id.groupID()).resolve(id.name()).resolve(id.version()).toFile();
final ComponentProvider provider =
new ComponentProvider() {
@Override
public File componentLocation(String id) {
return new File(appHome, id);
}
@Override
public File[] component(String componentID) {
final File dir = componentLocation(componentID);
final File[] files = dir.listFiles(File::isFile);
return files == null ? new File[0] : files;
}
@Override
public void defineComponent(String componentID, File[] components) {
final File dir = componentLocation(componentID);
if (dir.exists()) {
final StringBuilder files = new StringBuilder();
for (final File file : components) {
if (files.length() > 0) {
files.append(',');
}
files.append(file.toString());
}
throw new RuntimeException(
"Cannot redefine component. ID: " + id + ", files: " + files);
} else {
try {
copy(components, dir);
} catch (final IOException e) {
e.printStackTrace(System.err);
}
}
}
@Override
public boolean addToComponent(String componentID, File[] components) {
try {
boolean result = copy(components, componentLocation(componentID));
final File[] extra = componentLocation(componentID).listFiles();
classpathExtra.set(extra == null ? new File[0] : extra);
return result;
} catch (final IOException e) {
return true;
}
}
@Override
public File lockFile() {
return new File(appHome, "sbt.components.lock");
}
};
assert (libDir.exists());
final File[] jars = libDir.listFiles(f -> f.isFile() && f.getName().endsWith(".jar"));
final URL[] urls = new URL[jars.length];
@ -463,51 +505,43 @@ public class ScriptedLauncher {
@Override
public ComponentProvider components() {
return new ComponentProvider() {
@Override
public File componentLocation(String id) {
return new File(appHome, id);
}
@Override
public File[] component(String componentID) {
final File dir = componentLocation(componentID);
final File[] files = dir.listFiles(File::isFile);
return files == null ? new File[0] : files;
}
@Override
public void defineComponent(String componentID, File[] components) {
final File dir = componentLocation(componentID);
if (dir.exists()) {
final StringBuilder files = new StringBuilder();
for (final File file : components) {
if (files.length() > 0) {
files.append(',');
}
files.append(file.toString());
}
throw new RuntimeException(
"Cannot redefine component. ID: " + id + ", files: " + files);
} else {
copy(components, dir);
}
}
@Override
public boolean addToComponent(String componentID, File[] components) {
copy(components, componentLocation(componentID));
return false;
}
@Override
public File lockFile() {
return new File(appHome, "sbt.components.lock");
}
};
return provider;
}
};
}
};
}
private static void deleteRecursive(final File directory) {
try {
Files.walkFileTree(
directory.toPath(),
new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (attrs.isRegularFile()) Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.deleteIfExists(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (final IOException e) {
}
}
}