From aba6ab129991d100508c1e13e28f597813b89bc3 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 11 Aug 2015 19:13:28 -0700 Subject: [PATCH] Refactor command-line interface to kotlin-preloader.jar Use reasonable defaults for the options: no time profiling, no instrumenters, empty classpath, 4096 as the class number estimate. Replace 4096 in the codebase with the constant field. Keep the old interface intact until the build is bootstrapped and the new one can be used in all compilation steps --- .../jetbrains/kotlin/ant/KotlinAntTaskUtil.kt | 5 +- build.xml | 5 +- compiler/cli/bin/kotlinc | 4 +- compiler/cli/bin/kotlinc.bat | 4 +- .../kotlin/preloading/Preloader.java | 200 +++++++++++------- .../kotlin/test/MockLibraryUtil.java | 5 +- .../compilerRunner/CompilerRunnerUtil.java | 3 +- 7 files changed, 144 insertions(+), 82 deletions(-) diff --git a/ant/src/org/jetbrains/kotlin/ant/KotlinAntTaskUtil.kt b/ant/src/org/jetbrains/kotlin/ant/KotlinAntTaskUtil.kt index 57108c33a85..7a4d993f8b9 100644 --- a/ant/src/org/jetbrains/kotlin/ant/KotlinAntTaskUtil.kt +++ b/ant/src/org/jetbrains/kotlin/ant/KotlinAntTaskUtil.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.ant import org.apache.tools.ant.AntClassLoader import org.jetbrains.kotlin.preloading.ClassPreloadingUtils +import org.jetbrains.kotlin.preloading.Preloader import java.io.File import java.lang.ref.SoftReference import java.net.JarURLConnection @@ -54,10 +55,10 @@ object KotlinAntTaskUtil { val cached = classLoaderRef.get() if (cached != null) return cached - val myLoader = javaClass.getClassLoader() + val myLoader = javaClass.classLoader if (myLoader !is AntClassLoader) return myLoader - val classLoader = ClassPreloadingUtils.preloadClasses(listOf(compilerJar), 4096, myLoader, null) + val classLoader = ClassPreloadingUtils.preloadClasses(listOf(compilerJar), Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE, myLoader, null) classLoaderRef = SoftReference(classLoader) return classLoader diff --git a/build.xml b/build.xml index f3478978e65..2549381d9e6 100644 --- a/build.xml +++ b/build.xml @@ -278,9 +278,9 @@ + - @@ -415,6 +415,7 @@ + @@ -646,9 +647,9 @@ + - diff --git a/compiler/cli/bin/kotlinc b/compiler/cli/bin/kotlinc index fcc15150836..64a275e304a 100755 --- a/compiler/cli/bin/kotlinc +++ b/compiler/cli/bin/kotlinc @@ -71,5 +71,5 @@ fi "${java_args[@]}" \ -cp "${KOTLIN_HOME}/lib/kotlin-preloader.jar" \ org.jetbrains.kotlin.preloading.Preloader \ - "${KOTLIN_HOME}/lib/kotlin-compiler.jar" \ - $KOTLIN_COMPILER 4096 notime "$@" + -cp "${KOTLIN_HOME}/lib/kotlin-compiler.jar" \ + $KOTLIN_COMPILER "$@" diff --git a/compiler/cli/bin/kotlinc.bat b/compiler/cli/bin/kotlinc.bat index 820d634ace1..c1ae05d10c6 100644 --- a/compiler/cli/bin/kotlinc.bat +++ b/compiler/cli/bin/kotlinc.bat @@ -28,8 +28,8 @@ rem We use the value of the JAVA_OPTS environment variable if defined set _JAVA_OPTS=-Xmx256M -Xms32M -noverify "%_JAVACMD%" %_JAVA_OPTS% -cp "%_KOTLIN_HOME%\lib\kotlin-preloader.jar" ^ - org.jetbrains.kotlin.preloading.Preloader "%_KOTLIN_HOME%\lib\kotlin-compiler.jar" ^ - %KOTLIN_COMPILER% 4096 notime %* + org.jetbrains.kotlin.preloading.Preloader -cp "%_KOTLIN_HOME%\lib\kotlin-compiler.jar" ^ + %KOTLIN_COMPILER% %* exit /b %ERRORLEVEL% goto end diff --git a/compiler/preloader/src/org/jetbrains/kotlin/preloading/Preloader.java b/compiler/preloader/src/org/jetbrains/kotlin/preloading/Preloader.java index a1bf06a444c..d22fbb68be1 100644 --- a/compiler/preloader/src/org/jetbrains/kotlin/preloading/Preloader.java +++ b/compiler/preloader/src/org/jetbrains/kotlin/preloading/Preloader.java @@ -19,87 +19,150 @@ package org.jetbrains.kotlin.preloading; import org.jetbrains.kotlin.preloading.instrumentation.Instrumenter; import java.io.File; +import java.io.PrintStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; +@SuppressWarnings({"CallToPrintStackTrace", "UseOfSystemOutOrSystemErr"}) public class Preloader { - - public static final int PRELOADER_ARG_COUNT = 4; - private static final String INSTRUMENT_PREFIX = "instrument="; + public static final int DEFAULT_CLASS_NUMBER_ESTIMATE = 4096; public static void main(String[] args) throws Exception { - if (args.length < PRELOADER_ARG_COUNT) { - printUsageAndExit(); - } - - List files = parseClassPath(args[0]); - - String mainClassCanonicalName = args[1]; - - int classNumber; try { - classNumber = Integer.parseInt(args[2]); + run(args); } - catch (NumberFormatException e) { - System.err.println("error: number expected: " + e.getMessage()); - printUsageAndExit(); - return; + catch (Throwable e) { + System.err.println("error: " + e.toString()); + e.printStackTrace(); + System.err.println(); + printUsage(System.err); + System.exit(1); } + } - final Mode mode = parseMode(args[3]); - + private static void run(String[] args) throws Exception { final long startTime = System.nanoTime(); - ClassLoader parent = Preloader.class.getClassLoader(); + final Options options = parseOptions(args); - ClassLoader withInstrumenter = - mode instanceof Mode.Instrument ? new URLClassLoader(((Mode.Instrument) mode).classpath, parent) : parent; + ClassLoader classLoader = createClassLoader(options); - final Handler handler = getHandler(mode, withInstrumenter); - ClassLoader preloaded = ClassPreloadingUtils.preloadClasses(files, classNumber, withInstrumenter, null, handler); + final Handler handler = getHandler(options, classLoader); + ClassLoader preloaded = ClassPreloadingUtils.preloadClasses(options.classpath, options.estimate, classLoader, null, handler); - Class mainClass = preloaded.loadClass(mainClassCanonicalName); + Class mainClass = preloaded.loadClass(options.mainClass); Method mainMethod = mainClass.getMethod("main", String[].class); Runtime.getRuntime().addShutdownHook( new Thread(new Runnable() { @Override public void run() { - if (mode != Mode.NO_TIME) { + if (options.measure) { System.out.println(); System.out.println("=== Preloader's measurements: "); - long dt = System.nanoTime() - startTime; - System.out.format("Total time: %.3fs\n", dt / 1e9); + System.out.format("Total time: %.3fs\n", (System.nanoTime() - startTime) / 1e9); } handler.done(); } }) ); - mainMethod.invoke(0, new Object[] {Arrays.copyOfRange(args, PRELOADER_ARG_COUNT, args.length)}); + //noinspection SSBasedInspection + mainMethod.invoke(0, (Object) options.arguments.toArray(new String[options.arguments.size()])); + } + + private static ClassLoader createClassLoader(Options options) throws MalformedURLException { + ClassLoader parent = Preloader.class.getClassLoader(); + + List instrumenters = options.instrumenters; + if (instrumenters.isEmpty()) return parent; + + URL[] classpath = new URL[instrumenters.size()]; + for (int i = 0; i < instrumenters.size(); i++) { + classpath[i] = instrumenters.get(i).toURI().toURL(); + } + + return new URLClassLoader(classpath, parent); + } + + @SuppressWarnings({"AssignmentToForLoopParameter", "ConstantConditions"}) + private static Options parseOptions(String[] args) throws Exception { + // TODO: remove this temporary workaround as soon as the new command-line interface is bootstrapped + if (args.length >= 3 && "4096".equals(args[2])) { + //noinspection deprecation + return oldOptions(args); + } + + List classpath = Collections.emptyList(); + boolean measure = false; + List instrumenters = Collections.emptyList(); + int estimate = DEFAULT_CLASS_NUMBER_ESTIMATE; + String mainClass = null; + List arguments = new ArrayList(); + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + boolean end = i == args.length - 1; + + if ("-help".equals(arg) || "-h".equals(arg)) { + printUsage(System.out); + System.exit(0); + } + else if ("-cp".equals(arg) || "-classpath".equals(arg)) { + if (end) throw new RuntimeException("no argument provided to " + arg); + classpath = parseClassPath(args[++i]); + } + else if ("-estimate".equals(arg)) { + if (end) throw new RuntimeException("no argument provided to " + arg); + estimate = Integer.parseInt(args[++i]); + } + else if ("-instrument".equals(arg)) { + if (end) throw new RuntimeException("no argument provided to " + arg); + instrumenters = parseClassPath(args[++i]); + } + else if ("-measure".equals(arg)) { + measure = true; + } + else { + mainClass = arg; + arguments.addAll(Arrays.asList(args).subList(i + 1, args.length)); + break; + } + } + + if (mainClass == null) throw new RuntimeException("no main class name provided"); + + return new Options(classpath, measure, instrumenters, estimate, mainClass, arguments); + } + + @Deprecated + private static Options oldOptions(String[] args) { + return new Options( + parseClassPath(args[0]), false, Collections.emptyList(), 4096, args[1], + Arrays.asList(Arrays.copyOfRange(args, 4, args.length)) + ); } private static List parseClassPath(String classpath) { - String[] paths = classpath.split("\\" + File.pathSeparator); + String[] paths = classpath.split(File.pathSeparator); List files = new ArrayList(paths.length); for (String path : paths) { File file = new File(path); if (!file.exists()) { - System.err.println("error: file does not exist: " + file); - printUsageAndExit(); + throw new RuntimeException("file does not exist: " + file); } files.add(file); } return files; } - private static Handler getHandler(Mode mode, ClassLoader withInstrumenter) { - if (mode == Mode.NO_TIME) return new Handler(); + private static Handler getHandler(Options options, ClassLoader withInstrumenter) { + if (!options.measure) return new Handler(); - final Instrumenter instrumenter = mode instanceof Mode.Instrument ? loadInstrumenter(withInstrumenter) : Instrumenter.DO_NOTHING; + final Instrumenter instrumenter = options.instrumenters.isEmpty() ? Instrumenter.DO_NOTHING : loadInstrumenter(withInstrumenter); final int[] counter = new int[1]; final int[] size = new int[1]; @@ -143,47 +206,40 @@ public class Preloader { } } - private interface Mode { - Mode NO_TIME = new Mode() {}; - Mode TIME = new Mode() {}; - - class Instrument implements Mode { - public final URL[] classpath; - - Instrument(URL[] classpath) { - this.classpath = classpath; - } - } + private static void printUsage(PrintStream out) { + out.println("usage: java -jar kotlin-preloader.jar [] []"); + out.println("where possible options include:"); + out.println(" -classpath (-cp) Paths where to find class files"); + out.println(" -measure Record and output the total time taken by the program and number of loaded classes"); + out.println(" -instrument Paths where the instrumenter will be looked up by java.util.ServiceLoader"); + out.println(" (the class must implement " + Instrumenter.class.getCanonicalName() + " interface)"); + out.println(" -estimate Class number estimate (" + DEFAULT_CLASS_NUMBER_ESTIMATE + " by default)"); + out.println(" -help (-h) Output this help message"); } - private static Mode parseMode(String arg) { - if ("time".equals(arg)) return Mode.TIME; - if ("notime".equals(arg)) return Mode.NO_TIME; + private static class Options { + public final List classpath; + public final boolean measure; + public final List instrumenters; + public final int estimate; + public final String mainClass; + public final List arguments; - if (arg.startsWith(INSTRUMENT_PREFIX)) { - List files = parseClassPath(arg.substring(INSTRUMENT_PREFIX.length())); - URL[] classpath = new URL[files.size()]; - for (int i = 0; i < files.size(); i++) { - File file = files.get(i); - try { - classpath[i] = file.toURI().toURL(); - } - catch (MalformedURLException e) { - System.err.println("error: malformed URL: " + e.getMessage()); - printUsageAndExit(); - } - } - return new Mode.Instrument(classpath); + private Options( + List classpath, + boolean measure, + List instrumenters, + int estimate, + String mainClass, + List arguments + ) { + this.classpath = classpath; + this.measure = measure; + this.instrumenters = instrumenters; + this.estimate = estimate; + this.mainClass = mainClass; + this.arguments = arguments; } - - System.err.println("error: unrecognized argument: " + arg); - printUsageAndExit(); - return Mode.NO_TIME; - } - - private static void printUsageAndExit() { - System.out.println("Usage: Preloader
> "); - System.exit(1); } private static class Handler extends ClassHandler { diff --git a/compiler/tests/org/jetbrains/kotlin/test/MockLibraryUtil.java b/compiler/tests/org/jetbrains/kotlin/test/MockLibraryUtil.java index f4904f60360..06b28eaa21a 100644 --- a/compiler/tests/org/jetbrains/kotlin/test/MockLibraryUtil.java +++ b/compiler/tests/org/jetbrains/kotlin/test/MockLibraryUtil.java @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.cli.js.K2JSCompiler; import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; import org.jetbrains.kotlin.preloading.ClassPreloadingUtils; +import org.jetbrains.kotlin.preloading.Preloader; import org.jetbrains.kotlin.utils.PathUtil; import org.jetbrains.kotlin.utils.UtilsPackage; @@ -228,7 +229,9 @@ public class MockLibraryUtil { private static synchronized ClassLoader createCompilerClassLoader() { try { File kotlinCompilerJar = new File(PathUtil.getKotlinPathsForDistDirectory().getLibPath(), "kotlin-compiler.jar"); - return ClassPreloadingUtils.preloadClasses(Collections.singletonList(kotlinCompilerJar), 4096, null, null, null); + return ClassPreloadingUtils.preloadClasses( + Collections.singletonList(kotlinCompilerJar), Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE, null, null, null + ); } catch (Throwable e) { throw UtilsPackage.rethrow(e); diff --git a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/CompilerRunnerUtil.java b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/CompilerRunnerUtil.java index 75036a84842..47bee730d39 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/CompilerRunnerUtil.java +++ b/jps-plugin/src/org/jetbrains/kotlin/compilerRunner/CompilerRunnerUtil.java @@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.cli.common.messages.MessageCollector; import org.jetbrains.kotlin.preloading.ClassPreloadingUtils; +import org.jetbrains.kotlin.preloading.Preloader; import org.jetbrains.kotlin.utils.KotlinPaths; import java.io.File; @@ -45,7 +46,7 @@ public class CompilerRunnerUtil { if (classLoader == null) { classLoader = ClassPreloadingUtils.preloadClasses( Collections.singletonList(new File(libPath, "kotlin-compiler.jar")), - /* estimatedClassNumber = */ 4096, + Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE, CompilerRunnerUtil.class.getClassLoader(), environment.getClassesToLoadByParent() );