Skip to content

Commit

Permalink
Refactor command-line interface to kotlin-preloader.jar
Browse files Browse the repository at this point in the history
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
  • Loading branch information
udalov committed Aug 23, 2015
1 parent a592b42 commit aba6ab1
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 83 deletions.
5 changes: 3 additions & 2 deletions ant/src/org/jetbrains/kotlin/ant/KotlinAntTaskUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,9 @@
<assertions>
<enable/>
</assertions>
<arg value="-cp"/>
<arg value="${kotlin-home}/lib/kotlin-compiler.jar"/>
<arg value="org.jetbrains.kotlin.cli.js.K2JSCompiler"/>
<arg line="4096 notime"/>
<arg line="${src.line}"/>
<arg value="-output"/>
<arg value="@{output}"/>
Expand Down Expand Up @@ -415,6 +415,7 @@
<assertions>
<enable/>
</assertions>
<!-- TODO: use the new command-line interface to preloader here -->
<arg value="${bootstrap.compiler.home}/lib/kotlin-compiler.jar"/>
<arg value="org.jetbrains.kotlin.serialization.builtins.BuiltinsPackage"/>
<arg value="4096"/>
Expand Down Expand Up @@ -646,9 +647,9 @@
<assertions>
<enable/>
</assertions>
<arg value="-cp"/>
<arg value="${kotlin-home}/lib/kotlin-compiler.jar"/>
<arg value="org.jetbrains.kotlin.cli.jvm.K2JVMCompiler"/>
<arg line="4096 notime"/>
<arg line="${src.line}"/>
<arg value="-d"/>
<arg value="@{output}"/>
Expand Down
4 changes: 2 additions & 2 deletions compiler/cli/bin/kotlinc
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$@"
4 changes: 2 additions & 2 deletions compiler/cli/bin/kotlinc.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
202 changes: 129 additions & 73 deletions compiler/preloader/src/org/jetbrains/kotlin/preloading/Preloader.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,87 +19,150 @@
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<File> 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<File> 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<File> classpath = Collections.emptyList();
boolean measure = false;
List<File> instrumenters = Collections.emptyList();
int estimate = DEFAULT_CLASS_NUMBER_ESTIMATE;
String mainClass = null;
List<String> arguments = new ArrayList<String>();

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.<File>emptyList(), 4096, args[1],
Arrays.asList(Arrays.copyOfRange(args, 4, args.length))
);
}

private static List<File> parseClassPath(String classpath) {
String[] paths = classpath.split("\\" + File.pathSeparator);
String[] paths = classpath.split(File.pathSeparator);
List<File> files = new ArrayList<File>(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];
Expand Down Expand Up @@ -143,47 +206,40 @@ private static Instrumenter loadInstrumenter(ClassLoader withInstrumenter) {
}
}

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 [<preloader-options>] <main-class> [<main-class-arguments>]");
out.println("where possible options include:");
out.println(" -classpath (-cp) <paths> 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> 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 <number> 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;

if (arg.startsWith(INSTRUMENT_PREFIX)) {
List<File> 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 static class Options {
public final List<File> classpath;
public final boolean measure;
public final List<File> instrumenters;
public final int estimate;
public final String mainClass;
public final List<String> arguments;

private Options(
List<File> classpath,
boolean measure,
List<File> instrumenters,
int estimate,
String mainClass,
List<String> 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 <paths to jars> <main class> <class number estimate> <notime|time|instrument=<instrumenters class path>> <parameters to pass to the main class>");
System.exit(1);
}

private static class Handler extends ClassHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;

Expand Down Expand Up @@ -228,7 +229,9 @@ private static synchronized Class<?> loadCompilerClass(String compilerClassName)
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand All @@ -45,7 +46,7 @@ private static synchronized ClassLoader getOrCreateClassLoader(
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()
);
Expand Down

0 comments on commit aba6ab1

Please sign in to comment.