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() );