From eb66651886c2e430dd100ccbc98ad3521ecc5b19 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Fri, 17 Jan 2020 17:23:30 -0800 Subject: [PATCH] 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. --- .../test/scala/sbt/RunFromSourceMain.scala | 18 +- .../scriptedtest/ScriptedLauncher.java | 380 ++++++++++-------- 2 files changed, 218 insertions(+), 180 deletions(-) diff --git a/sbt/src/test/scala/sbt/RunFromSourceMain.scala b/sbt/src/test/scala/sbt/RunFromSourceMain.scala index ad5a9ec97..927ccc6c3 100644 --- a/sbt/src/test/scala/sbt/RunFromSourceMain.scala +++ b/sbt/src/test/scala/sbt/RunFromSourceMain.scala @@ -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" diff --git a/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java b/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java index 95e642a2d..dbb623942 100644 --- a/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java +++ b/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java @@ -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 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 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 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() { + @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) { + } + } }