From 86eaf9d5728818fb8a20816c8d9d9f9fb4b7b7e4 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 12 Jan 2020 16:50:29 -0800 Subject: [PATCH] Allow RunSourceFromMain to use launcher The repo overrides scripted test relies on using the launcher to modify the default resolvers. To support this, I extended the scripted launcher to use the bundled sbt launcher if it is passed in via the `-Dsbt.launch.jar` system property. --- build.sbt | 13 +- .../scriptedtest/ScriptedLauncher.java | 152 +++++++++++------- 2 files changed, 104 insertions(+), 61 deletions(-) diff --git a/build.sbt b/build.sbt index 991885d82..940c5158a 100644 --- a/build.sbt +++ b/build.sbt @@ -1099,15 +1099,16 @@ lazy val vscodePlugin = (project in file("vscode-sbt-scala")) } ) -def scriptedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask { +def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTask { publishLocalBinAll.value + val launchJar = s"-Dsbt.launch.jar=${(bundledLauncherProj / Compile / packageBin).value}" Scripted.doScripted( (scalaInstance in scriptedSbtReduxProj).value, scriptedSource.value, scriptedBufferLog.value, Def.setting(Scripted.scriptedParser(scriptedSource.value)).parsed, scriptedPrescripted.value, - scriptedLaunchOpts.value, + scriptedLaunchOpts.value ++ (if (launch) Some(launchJar) else None), scalaVersion.value, version.value, (scriptedSbtReduxProj / Test / fullClasspathAsJars).value.map(_.data), @@ -1161,8 +1162,8 @@ ThisBuild / scriptedPrescripted := { _ => def otherRootSettings = Seq( - scripted := scriptedTask.evaluated, - scriptedUnpublished := scriptedTask.evaluated, + scripted := scriptedTask(false).evaluated, + scriptedUnpublished := scriptedTask(false).evaluated, scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test", watchTriggers in scripted += scriptedSource.value.toGlob / **, watchTriggers in scriptedUnpublished := (watchTriggers in scripted).value, @@ -1186,8 +1187,8 @@ def otherRootSettings = case Some(home) => List(s"-Dsbt.ivy.home=$home") case _ => Nil }), - scripted := scriptedTask.evaluated, - scriptedUnpublished := scriptedTask.evaluated, + scripted := scriptedTask(true).evaluated, + scriptedUnpublished := scriptedTask(true).evaluated, scriptedSource := (sourceDirectory in sbtProj).value / "repo-override-test" ) ) diff --git a/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java b/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java index 746f5bc22..95e642a2d 100644 --- a/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java +++ b/sbt/src/test/scala/sbt/internal/scriptedtest/ScriptedLauncher.java @@ -9,6 +9,7 @@ package sbt.internal.scriptedtest; import java.io.File; import java.io.IOException; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; @@ -24,8 +25,10 @@ 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; @@ -54,59 +57,90 @@ public class ScriptedLauncher { String[] args) throws MalformedURLException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException { - 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}, ClassLoader.getSystemClassLoader().getParent()); - final URLClassLoader loader = new URLClassLoader(new URL[] {mainURL, scriptedURL}, topLoader); + 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 Class clazz = loader.loadClass("sbt.internal.scriptedtest.ScriptedLauncher"); + Method method = + clazz.getDeclaredMethod( + "launchImpl", + ClassLoader.class, + ClassLoader.class, + File.class, + String.class, + String.class, + File.class, + 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); + } + } + } else { + final URL url = new URL("file:" + System.getProperty("sbt.launch.jar")); + final URLClassLoader loader = new URLClassLoader(new URL[] {url}, top()); + final Class boot = loader.loadClass("xsbt.boot.Boot"); + // If we don't initialize the arguments this way, then the call to invoke on + // xsbt.boot.Boot.main fails with an IllegalArgumentException + final Object newArgs = Array.newInstance(loader.loadClass("java.lang.String"), args.length); + for (int i = 0; i < args.length; ++i) ((String[]) newArgs)[i] = args[i]; final ClassLoader previous = Thread.currentThread().getContextClassLoader(); + try { Thread.currentThread().setContextClassLoader(loader); - final AtomicInteger result = new AtomicInteger(-1); - final AtomicReference newArguments = new AtomicReference<>(); - final Class clazz = loader.loadClass("sbt.internal.scriptedtest.ScriptedLauncher"); - Method method = - clazz.getDeclaredMethod( - "launchImpl", - ClassLoader.class, - ClassLoader.class, - File.class, - String.class, - String.class, - File.class, - 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(); + boot.getDeclaredMethod("main", newArgs.getClass()).invoke(null, newArgs); + return Optional.empty(); } finally { - try { - loader.close(); - } catch (final Exception e) { - } - Thread.currentThread().setContextClassLoader(previous); + swap(loader, previous); } } } + private static ClassLoader top() { + ClassLoader result = ClassLoader.getSystemClassLoader(); + while (result.getParent() != null) result = result.getParent(); + return result; + } + + private static void swap(final URLClassLoader old, final ClassLoader stashed) { + try { + old.close(); + } catch (final Exception e) { + } + Thread.currentThread().setContextClassLoader(stashed); + } + private static void copy(final File[] files, final File toDirectory) { for (final File file : files) { try { @@ -146,16 +180,24 @@ public class ScriptedLauncher { final Class clazz = loader.loadClass("sbt.xMain"); final Object instance = clazz.getConstructor().newInstance(); final Method run = clazz.getDeclaredMethod("run", loader.loadClass("xsbti.AppConfiguration")); - final Object runResult = run.invoke(instance, conf); - if (runResult instanceof xsbti.Reboot) newArguments.set(((Reboot) runResult).arguments()); - else { - if (runResult instanceof xsbti.Exit) { - result.set(((Exit) runResult).code()); - } else if (runResult instanceof xsbti.Continue) { - result.set(Integer.MAX_VALUE); - } else { - handleUnknownMainResult((MainResult) runResult); - } + 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); } }