From 9273b0dfec430ea89536f2238f7c4c6eff1e9f83 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 26 Dec 2014 17:14:35 +0300 Subject: [PATCH] Preloader: support loading classpath dependencies from manifest Move this logic from ide-compiler-runner. This fixes running 'kotlinc' CLI compiler locally when the runtime is changed binary incompatibly --- compiler/cli/bin/kotlinc-js | 5 +- compiler/cli/bin/kotlinc-js.bat | 2 +- compiler/cli/bin/kotlinc-jvm | 5 +- compiler/cli/bin/kotlinc-jvm.bat | 2 +- .../jet/preloading/ClassPreloadingUtils.java | 45 ++++++++++++++ .../compiler/runner/CompilerRunnerUtil.java | 59 ++----------------- 6 files changed, 55 insertions(+), 63 deletions(-) diff --git a/compiler/cli/bin/kotlinc-js b/compiler/cli/bin/kotlinc-js index 70f93d3c639..762b9192449 100755 --- a/compiler/cli/bin/kotlinc-js +++ b/compiler/cli/bin/kotlinc-js @@ -32,13 +32,10 @@ done; KOTLIN_HOME=`dirname "$SOURCE"` KOTLIN_HOME=`cd "$KOTLIN_HOME"; pwd -P` KOTLIN_HOME=`cd "$KOTLIN_HOME"/..; pwd` -PATH_SEPARATOR=":" if $cygwin; then # Remove spaces from KOTLIN_HOME on windows KOTLIN_HOME=`cygpath --windows --short-name "$KOTLIN_HOME"` - - PATH_SEPARATOR=";" fi [ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx256M -Xms32M -noverify" @@ -86,5 +83,5 @@ CPSELECT="-cp " "${java_args[@]}" \ ${CPSELECT}"${KOTLIN_HOME}/lib/kotlin-preloader.jar" \ org.jetbrains.jet.preloading.Preloader \ - "${KOTLIN_HOME}/lib/kotlin-compiler.jar${PATH_SEPARATOR}${KOTLIN_HOME}/lib/kotlin-runtime.jar" \ + "${KOTLIN_HOME}/lib/kotlin-compiler.jar" \ org.jetbrains.jet.cli.js.K2JSCompiler 4096 notime "$@" diff --git a/compiler/cli/bin/kotlinc-js.bat b/compiler/cli/bin/kotlinc-js.bat index 021a1e18e2a..7299f624dc9 100644 --- a/compiler/cli/bin/kotlinc-js.bat +++ b/compiler/cli/bin/kotlinc-js.bat @@ -26,7 +26,7 @@ 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.jet.preloading.Preloader "%_KOTLIN_HOME%\lib\kotlin-compiler.jar;%_KOTLIN_HOME%\lib\kotlin-runtime.jar" ^ + org.jetbrains.jet.preloading.Preloader "%_KOTLIN_HOME%\lib\kotlin-compiler.jar" ^ org.jetbrains.jet.cli.js.K2JSCompiler 4096 notime %* exit /b %ERRORLEVEL% diff --git a/compiler/cli/bin/kotlinc-jvm b/compiler/cli/bin/kotlinc-jvm index c61e638ad11..25572ccc4ac 100755 --- a/compiler/cli/bin/kotlinc-jvm +++ b/compiler/cli/bin/kotlinc-jvm @@ -32,13 +32,10 @@ done; KOTLIN_HOME=`dirname "$SOURCE"` KOTLIN_HOME=`cd "$KOTLIN_HOME"; pwd -P` KOTLIN_HOME=`cd "$KOTLIN_HOME"/..; pwd` -PATH_SEPARATOR=":" if $cygwin; then # Remove spaces from KOTLIN_HOME on windows KOTLIN_HOME=`cygpath --windows --short-name "$KOTLIN_HOME"` - - PATH_SEPARATOR=";" fi [ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx256M -Xms32M -noverify" @@ -86,5 +83,5 @@ CPSELECT="-cp " "${java_args[@]}" \ ${CPSELECT}"${KOTLIN_HOME}/lib/kotlin-preloader.jar" \ org.jetbrains.jet.preloading.Preloader \ - "${KOTLIN_HOME}/lib/kotlin-compiler.jar${PATH_SEPARATOR}${KOTLIN_HOME}/lib/kotlin-runtime.jar" \ + "${KOTLIN_HOME}/lib/kotlin-compiler.jar" \ org.jetbrains.jet.cli.jvm.K2JVMCompiler 4096 notime "$@" diff --git a/compiler/cli/bin/kotlinc-jvm.bat b/compiler/cli/bin/kotlinc-jvm.bat index c7dac2c45ff..c5638e2c2ab 100644 --- a/compiler/cli/bin/kotlinc-jvm.bat +++ b/compiler/cli/bin/kotlinc-jvm.bat @@ -26,7 +26,7 @@ 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.jet.preloading.Preloader "%_KOTLIN_HOME%\lib\kotlin-compiler.jar;%_KOTLIN_HOME%\lib\kotlin-runtime.jar" ^ + org.jetbrains.jet.preloading.Preloader "%_KOTLIN_HOME%\lib\kotlin-compiler.jar" ^ org.jetbrains.jet.cli.jvm.K2JVMCompiler 4096 notime %* exit /b %ERRORLEVEL% diff --git a/compiler/preloader/src/org/jetbrains/jet/preloading/ClassPreloadingUtils.java b/compiler/preloader/src/org/jetbrains/jet/preloading/ClassPreloadingUtils.java index 48a3efc21b5..10882d7ebcb 100644 --- a/compiler/preloader/src/org/jetbrains/jet/preloading/ClassPreloadingUtils.java +++ b/compiler/preloader/src/org/jetbrains/jet/preloading/ClassPreloadingUtils.java @@ -22,6 +22,9 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -62,6 +65,11 @@ public class ClassPreloadingUtils { ) throws IOException { Map entries = loadAllClassesFromJars(jarFiles, classCountEstimation, handler); + Collection classpath = mergeClasspathFromManifests(entries); + if (!classpath.isEmpty()) { + parentClassLoader = preloadClasses(classpath, classCountEstimation, parentClassLoader, null, handler); + } + return createMemoryBasedClassLoader(parentClassLoader, entries, handler, classesToLoadByParent); } @@ -71,6 +79,43 @@ public class ClassPreloadingUtils { return preloadClasses(jarFiles, classCountEstimation, parentClassLoader, classesToLoadByParent, null); } + private static Collection mergeClasspathFromManifests(Map preloadedResources) throws IOException { + Object manifest = preloadedResources.get(JarFile.MANIFEST_NAME); + if (manifest instanceof ResourceData) { + return extractManifestClasspath((ResourceData) manifest); + } + else if (manifest instanceof ArrayList) { + List result = new ArrayList(); + for (ResourceData data : (ArrayList) manifest) { + result.addAll(extractManifestClasspath(data)); + } + return result; + } + else { + assert manifest == null : "Resource map should contain ResourceData or ArrayList: " + manifest; + return Collections.emptyList(); + } + } + + private static Collection extractManifestClasspath(ResourceData manifestData) throws IOException { + Manifest manifest = new Manifest(new ByteArrayInputStream(manifestData.bytes)); + String classpathSpaceSeparated = (String) manifest.getMainAttributes().get(Attributes.Name.CLASS_PATH); + if (classpathSpaceSeparated == null) return Collections.emptyList(); + + Collection classpath = new ArrayList(1); + for (String jar : classpathSpaceSeparated.split(" ")) { + if (".".equals(jar)) continue; + + if (!jar.endsWith(".jar")) { + throw new UnsupportedOperationException("Class-Path attribute should only contain paths to JAR files: " + jar); + } + + classpath.add(new File(manifestData.jarFile.getParent(), jar)); + } + + return classpath; + } + private static ClassLoader createMemoryBasedClassLoader( final ClassLoader parent, final Map preloadedResources, diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerRunnerUtil.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerRunnerUtil.java index d7f08c0da18..2bf1db81ca2 100644 --- a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerRunnerUtil.java +++ b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/CompilerRunnerUtil.java @@ -16,28 +16,18 @@ package org.jetbrains.jet.compiler.runner; -import kotlin.Function1; -import kotlin.KotlinPackage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.cli.common.messages.MessageCollector; -import org.jetbrains.jet.preloading.ClassCondition; import org.jetbrains.jet.preloading.ClassPreloadingUtils; import org.jetbrains.jet.utils.KotlinPaths; -import org.jetbrains.jet.utils.UtilsPackage; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.lang.ref.SoftReference; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; import java.util.Collections; -import java.util.List; -import java.util.jar.Attributes; -import java.util.jar.JarFile; import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION; import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERROR; @@ -53,7 +43,12 @@ public class CompilerRunnerUtil { ) throws IOException { ClassLoader classLoader = ourClassLoaderRef.get(); if (classLoader == null) { - classLoader = createClassLoader(libPath, CompilerRunnerUtil.class.getClassLoader(), environment.getClassesToLoadByParent()); + classLoader = ClassPreloadingUtils.preloadClasses( + Collections.singletonList(new File(libPath, "kotlin-compiler.jar")), + /* estimatedClassNumber = */ 4096, + CompilerRunnerUtil.class.getClassLoader(), + environment.getClassesToLoadByParent() + ); ourClassLoaderRef = new SoftReference(classLoader); } return classLoader; @@ -73,48 +68,6 @@ public class CompilerRunnerUtil { return null; } - @NotNull - private static File compilerJar(@NotNull File libPath) { - return new File(libPath, "kotlin-compiler.jar"); - } - - @NotNull - private static String loadCompilerClasspathSpaceSeparated(@NotNull File libPath) throws IOException { - JarFile jar = new JarFile(compilerJar(libPath)); - try { - return (String) jar.getManifest().getMainAttributes().get(Attributes.Name.CLASS_PATH); - } - finally { - jar.close(); - } - } - - @NotNull - private static ClassLoader createClassLoader( - @NotNull final File libPath, - @Nullable ClassLoader parentClassLoader, - @Nullable ClassCondition classToLoadByParent - ) throws IOException { - List classpath = KotlinPackage.map(loadCompilerClasspathSpaceSeparated(libPath).split(" "), new Function1() { - @Override - public URL invoke(String dependency) { - try { - return new File(libPath, dependency).toURI().toURL(); - } - catch (MalformedURLException e) { - throw UtilsPackage.rethrow(e); - } - } - }); - - return ClassPreloadingUtils.preloadClasses( - Collections.singletonList(compilerJar(libPath)), - /* estimatedClassNumber = */ 4096, - new URLClassLoader(classpath.toArray(new URL[classpath.size()]), parentClassLoader), - classToLoadByParent - ); - } - @Nullable public static Object invokeExecMethod( @NotNull String compilerClassName,