Merge pull request #739 from JetBrains/rr/gradle-plugin-classloading-M13-2
merging gradle plugin classloading changes
This commit is contained in:
@@ -93,7 +93,6 @@ messages/**)
|
||||
|
||||
# for gradle plugin and other server tools
|
||||
-keep class com.intellij.openapi.util.io.ZipFileCache { public *; }
|
||||
-keep class com.intellij.openapi.util.LowMemoryWatcher { public *; }
|
||||
|
||||
# for j2k
|
||||
-keep class com.intellij.codeInsight.NullableNotNullManager { public protected *; }
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-compiler</artifactId>
|
||||
<artifactId>kotlin-compiler-embeddable</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
|
||||
<modules>
|
||||
<module>tools/kotlin-compiler</module>
|
||||
<module>tools/kotlin-compiler-embeddable</module>
|
||||
<module>tools/kotlin-jdk-annotations</module>
|
||||
<module>tools/kotlin-android-sdk-annotations</module>
|
||||
<module>tools/kotlin-maven-plugin</module>
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<properties>
|
||||
<maven-plugin-anno.version>1.4.1</maven-plugin-anno.version>
|
||||
<maven.version>3.0.4</maven.version>
|
||||
<kotlin.relocated.package>org.jetbrains.kotlin.relocated</kotlin.relocated.package>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-project</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>kotlin-compiler-embeddable</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>the Kotlin compiler embeddable</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-compiler</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-artifacts</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>attach-artifact</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifacts>
|
||||
<artifact>
|
||||
<file>${kotlin-dist}/kotlin-compiler-sources.jar</file>
|
||||
<type>jar</type>
|
||||
<classifier>sources</classifier>
|
||||
</artifact>
|
||||
<artifact>
|
||||
<file>${kotlin-dist}/kotlin-compiler-javadoc.jar</file>
|
||||
<type>jar</type>
|
||||
<classifier>javadoc</classifier>
|
||||
</artifact>
|
||||
</artifacts>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>attach-empty-javadoc</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>attach-artifact</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<skipAttach>true</skipAttach>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:kotlin-runtime</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
<artifact>*:kotlin-reflect</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
<artifact>*:kotlin-stdlib</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>com.google</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.com.google</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.sampullara</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.com.sampullara</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.apache</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.org.apache</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.jdom</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.org.jdom</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.fusesource</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.org.fusesource</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.picocontainer</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.org.picocontainer</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>jline</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.jline</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>gnu</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.gnu</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>javax.inject</pattern>
|
||||
<shadedPattern>${kotlin.relocated.package}.javax.inject</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -44,7 +44,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-compiler</artifactId>
|
||||
<artifactId>kotlin-compiler-embeddable</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.openapi.util;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.containers.WeakList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.management.ListenerNotFoundException;
|
||||
import javax.management.Notification;
|
||||
import javax.management.NotificationEmitter;
|
||||
import javax.management.NotificationListener;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryNotificationInfo;
|
||||
import java.lang.management.MemoryPoolMXBean;
|
||||
import java.lang.management.MemoryType;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* PATCHED VERSION of com.intellij.openapi.util.LowMemoryWatcher
|
||||
* shutdown() method added to stop background thread.
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public class LowMemoryWatcher {
|
||||
private static final long MEM_THRESHOLD = 5 /*MB*/ * 1024 * 1024;
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.LowMemoryWatcher");
|
||||
|
||||
private static final List<LowMemoryWatcher> ourInstances = new WeakList<LowMemoryWatcher>();
|
||||
private static final ThreadPoolExecutor ourExecutor = new ThreadPoolExecutor(0, 1, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), ConcurrencyUtil.newNamedThreadFactory("LowMemoryWatcher janitor"));
|
||||
private static boolean ourSubmitted;
|
||||
private static final Runnable ourJanitor = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for (LowMemoryWatcher watcher : ourInstances) {
|
||||
try {
|
||||
watcher.myRunnable.run();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
synchronized (ourJanitor) {
|
||||
//noinspection AssignmentToStaticFieldFromInstanceMethod
|
||||
ourSubmitted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable myRunnable;
|
||||
|
||||
private static final NotificationListener lowMemoryListener;
|
||||
|
||||
static {
|
||||
for (MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
|
||||
if (bean.getType() == MemoryType.HEAP && bean.isUsageThresholdSupported()) {
|
||||
long threshold = bean.getUsage().getMax() - MEM_THRESHOLD;
|
||||
if (threshold > 0) {
|
||||
bean.setUsageThreshold(threshold);
|
||||
bean.setCollectionUsageThreshold(threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lowMemoryListener = new NotificationListener() {
|
||||
@Override
|
||||
public void handleNotification(@NotNull Notification n, Object hb) {
|
||||
if (MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED.equals(n.getType()) ||
|
||||
MemoryNotificationInfo.MEMORY_COLLECTION_THRESHOLD_EXCEEDED.equals(n.getType())) {
|
||||
synchronized (ourJanitor) {
|
||||
if (!ourSubmitted) {
|
||||
//noinspection AssignmentToStaticFieldFromInstanceMethod
|
||||
ourSubmitted = true;
|
||||
ourExecutor.submit(ourJanitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
((NotificationEmitter)ManagementFactory.getMemoryMXBean()).addNotificationListener(lowMemoryListener, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a runnable to run on low memory events
|
||||
* @return a LowMemoryWatcher instance holding the runnable. This instance should be kept in memory while the
|
||||
* low memory notification functionality is needed. As soon as it's garbage-collected, the runnable won't receive any further notifications.
|
||||
*/
|
||||
public static LowMemoryWatcher register(Runnable runnable) {
|
||||
return new LowMemoryWatcher(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a runnable to run on low memory events. The notifications will be issued until parentDisposable is disposed.
|
||||
*/
|
||||
public static void register(Runnable runnable, Disposable parentDisposable) {
|
||||
final Ref<LowMemoryWatcher> watcher = Ref.create(new LowMemoryWatcher(runnable));
|
||||
Disposer.register(parentDisposable, new Disposable() {
|
||||
@Override
|
||||
public void dispose() {
|
||||
watcher.get().stop();
|
||||
watcher.set(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private LowMemoryWatcher(Runnable runnable) {
|
||||
myRunnable = runnable;
|
||||
ourInstances.add(this);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
ourInstances.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* LowMemoryWatcher maintains a background thread where all the handlers are invoked.
|
||||
* In server environments, this thread may run indefinitely and prevent the class loader from
|
||||
* being gc-ed. Thus it's necessary to invoke this method to stop that thread and let the classes be garbage-collected.
|
||||
*/
|
||||
public static void stopAll() throws InterruptedException, ListenerNotFoundException {
|
||||
ourInstances.clear();
|
||||
((NotificationEmitter)ManagementFactory.getMemoryMXBean()).removeNotificationListener(lowMemoryListener);
|
||||
|
||||
ourExecutor.shutdown();
|
||||
|
||||
boolean terminated = ourExecutor.awaitTermination(50, TimeUnit.MICROSECONDS);
|
||||
if (!terminated) {
|
||||
ourExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-gradle-plugin-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
|
||||
+64
-48
@@ -18,64 +18,85 @@ package org.jetbrains.kotlin.gradle.plugin
|
||||
|
||||
import org.gradle.BuildAdapter
|
||||
import org.gradle.BuildResult
|
||||
import org.gradle.api.invocation.Gradle
|
||||
import org.gradle.api.logging.Logging
|
||||
import java.lang.ref.Reference
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import kotlin.text.MatchGroup
|
||||
|
||||
class FinishBuildListener(var pluginClassLoader: ParentLastURLClassLoader?) : BuildAdapter() {
|
||||
internal fun getUsedMemoryKb(): Long {
|
||||
System.gc()
|
||||
val rt = Runtime.getRuntime()
|
||||
return (rt.totalMemory() - rt.freeMemory()) / 1024
|
||||
}
|
||||
|
||||
|
||||
private fun comparableVersionStr(version: String) =
|
||||
"(\\d+)\\.(\\d+).*"
|
||||
.toRegex()
|
||||
.match(version)
|
||||
?.groups
|
||||
?.drop(1)?.take(2)
|
||||
// checking if two subexpression groups are found and length of each is >0 and <4
|
||||
?.let { if (it.all { (it?.value?.length() ?: 0).let { it > 0 && it < 4 }}) it else null }
|
||||
?.joinToString(".", transform = { it!!.value.padStart(3, '0') })
|
||||
|
||||
|
||||
class FinishBuildListener(pluginClassLoader: ClassLoader?, val startMemory: Long) : BuildAdapter() {
|
||||
val log = Logging.getLogger(this.javaClass)
|
||||
|
||||
private var threadTracker: ThreadTracker? = ThreadTracker()
|
||||
|
||||
private val cleanup = CompilerServicesCleanup(pluginClassLoader)
|
||||
|
||||
override fun buildFinished(result: BuildResult?) {
|
||||
log.kotlinDebug("Build finished listener")
|
||||
|
||||
stopZipFileCache()
|
||||
stopLowMemoryWatcher()
|
||||
stopJobScheduler()
|
||||
|
||||
// TODO: Try to clean up thread locals without this ugly hack
|
||||
// TODO: Further investigation of PermGen leak (KT-6451)
|
||||
removeThreadLocals()
|
||||
|
||||
pluginClassLoader = null
|
||||
result?.getGradle()?.removeListener(this)
|
||||
|
||||
threadTracker?.checkThreadLeak(result?.getGradle())
|
||||
threadTracker = null
|
||||
}
|
||||
|
||||
public fun removeThreadLocals() {
|
||||
try {
|
||||
log.kotlinDebug("Remove ChildURLClassLoader thread locals")
|
||||
|
||||
val thread = Thread.currentThread()
|
||||
val threadLocalsField = javaClass<Thread>().getDeclaredField("threadLocals")
|
||||
threadLocalsField.setAccessible(true)
|
||||
|
||||
val threadLocalMapClass = Class.forName("java.lang.ThreadLocal\$ThreadLocalMap")
|
||||
val tableField = threadLocalMapClass.getDeclaredField("table")
|
||||
tableField.setAccessible(true)
|
||||
|
||||
val referentField = javaClass<Reference<Any>>().getDeclaredField("referent")
|
||||
referentField.setAccessible(true)
|
||||
|
||||
val table = tableField[threadLocalsField[thread]] as Array<*>
|
||||
|
||||
for (entry in table) {
|
||||
if (entry != null) {
|
||||
val threadLocal = referentField[entry] as ThreadLocal<*>?
|
||||
val classLoader = threadLocal?.javaClass?.getClassLoader()
|
||||
if (classLoader is ParentLastURLClassLoader.ChildURLClassLoader) {
|
||||
threadLocal?.remove()
|
||||
}
|
||||
val gradle = result?.getGradle()
|
||||
if (gradle != null) {
|
||||
// making cleanup only on recognized versions of gradle and if version < 2.4
|
||||
// otherwise it may cause problems e.g. with JobScheduler on subsequent runs
|
||||
// this strategy may lead to memory leaks, but prevent crashes due to destroyed JobScheduler
|
||||
// the reason for the strategy is the following:
|
||||
// gradle < 2.4 has problems with plugin reuse in the daemon: new calls to the plugin are made with a new classloader
|
||||
// for every new build. With statically initialized daemons like JobScheduler that leads to big leaks of classloaders and classes,
|
||||
// therefore to reduce leaks JobScheduler (and deprecated ZipFileCache for now) should be stopped)
|
||||
// It should be noted that because of this behavior there are no benefits of using daemon in these versions.
|
||||
// Starting from 2.4 gradle using cached classloaders, that leads to effective class reusing in the daemon, but
|
||||
// in that case premature stopping of the static daemons may lead to crashes.
|
||||
comparableVersionStr(gradle.getGradleVersion())?.let {
|
||||
log.kotlinDebug("detected gradle version $it")
|
||||
if (it < comparableVersionStr("2.4")!!) {
|
||||
cleanup()
|
||||
// checking thread leaks only then cleaning up
|
||||
threadTracker?.checkThreadLeak(gradle)
|
||||
}
|
||||
}
|
||||
|
||||
log.kotlinDebug("Removing ChildURLClassLoader thread locals finished successfully")
|
||||
} catch (e: Throwable) {
|
||||
log.kotlinDebug("Exception during thread locals remove: " + e)
|
||||
threadTracker = null
|
||||
gradle.removeListener(this)
|
||||
}
|
||||
|
||||
// the value reported here is not necessarily a leak, since it is calculated before collecting the plugin classes
|
||||
// but on subsequent runs in the daemon it should be rather small, then the classes are actually reused by the daemon (see above)
|
||||
getUsedMemoryKb().let { log.kotlinDebug("[PERF] Used memory after build: $it kb (${"%+d".format(it - startMemory)} kb)") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CompilerServicesCleanup(private var pluginClassLoader: ClassLoader?) {
|
||||
val log = Logging.getLogger(this.javaClass)
|
||||
|
||||
fun invoke() {
|
||||
assert(pluginClassLoader != null)
|
||||
|
||||
log.kotlinDebug("compiler services cleanup")
|
||||
|
||||
// TODO: remove ZipFileCache cleanup after switching to recent idea libs
|
||||
stopZipFileCache()
|
||||
stopJobScheduler()
|
||||
|
||||
pluginClassLoader = null
|
||||
}
|
||||
|
||||
private fun stopZipFileCache() {
|
||||
@@ -83,11 +104,6 @@ class FinishBuildListener(var pluginClassLoader: ParentLastURLClassLoader?) : Bu
|
||||
log.kotlinDebug("ZipFileCache finished successfully")
|
||||
}
|
||||
|
||||
private fun stopLowMemoryWatcher() {
|
||||
callVoidStaticMethod("com.intellij.openapi.util.LowMemoryWatcher", "stopAll")
|
||||
log.kotlinDebug("LowMemoryWatcher finished successfully")
|
||||
}
|
||||
|
||||
private fun stopJobScheduler() {
|
||||
log.kotlinDebug("Stop JobScheduler")
|
||||
|
||||
|
||||
+13
-28
@@ -13,12 +13,18 @@ import org.gradle.api.invocation.Gradle
|
||||
import org.gradle.api.logging.Logger
|
||||
import java.lang.reflect.Method
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
|
||||
import java.net.URLClassLoader
|
||||
|
||||
// TODO: simplify: the complicated structure is a leftover from dynamic loading of plugin core, could be significantly simplified now
|
||||
|
||||
abstract class KotlinBasePluginWrapper: Plugin<Project> {
|
||||
|
||||
val log = Logging.getLogger(this.javaClass)
|
||||
|
||||
public override fun apply(project: Project) {
|
||||
|
||||
val startMemory = getUsedMemoryKb()
|
||||
|
||||
val sourceBuildScript = findSourceBuildScript(project)
|
||||
if (sourceBuildScript == null) {
|
||||
log.error("Failed to determine source cofiguration of kotlin plugin. Can not download core. Please verify that this or any parent project " +
|
||||
@@ -29,33 +35,13 @@ abstract class KotlinBasePluginWrapper: Plugin<Project> {
|
||||
val kotlinPluginVersion = loadKotlinVersionFromResource(log)
|
||||
project.getExtensions().getExtraProperties()?.set("kotlin.gradle.plugin.version", kotlinPluginVersion)
|
||||
|
||||
val pluginClassLoader = createPluginIsolatedClassLoader(kotlinPluginVersion, sourceBuildScript)
|
||||
val plugin = getPlugin(pluginClassLoader, sourceBuildScript)
|
||||
val plugin = getPlugin(this.javaClass.getClassLoader(), sourceBuildScript)
|
||||
plugin.apply(project)
|
||||
|
||||
project.getGradle().addBuildListener(FinishBuildListener(pluginClassLoader))
|
||||
project.getGradle().addBuildListener(FinishBuildListener(this.javaClass.getClassLoader(), startMemory))
|
||||
}
|
||||
|
||||
protected abstract fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler): Plugin<Project>
|
||||
|
||||
private fun createPluginIsolatedClassLoader(projectVersion: String, sourceBuildScript: ScriptHandler): ParentLastURLClassLoader {
|
||||
val dependencyHandler: DependencyHandler = sourceBuildScript.getDependencies()
|
||||
val configurationsContainer: ConfigurationContainer = sourceBuildScript.getConfigurations()
|
||||
|
||||
log.kotlinDebug("Creating configuration and dependency")
|
||||
val kotlinPluginCoreCoordinates = "org.jetbrains.kotlin:kotlin-gradle-plugin-core:" + projectVersion
|
||||
val dependency = dependencyHandler.create(kotlinPluginCoreCoordinates)
|
||||
val configuration = configurationsContainer.detachedConfiguration(dependency)
|
||||
|
||||
log.kotlinDebug("Resolving [" + kotlinPluginCoreCoordinates + "]")
|
||||
val kotlinPluginDependencies: List<URL> = configuration.getResolvedConfiguration().getFiles({ true })!!.map { it.toURI().toURL() }
|
||||
log.kotlinDebug("Resolved files: [" + kotlinPluginDependencies.toString() + "]")
|
||||
log.kotlinDebug("Load plugin in parent-last URL classloader")
|
||||
val kotlinPluginClassloader = ParentLastURLClassLoader(kotlinPluginDependencies, this.javaClass.getClassLoader())
|
||||
log.kotlinDebug("Class loader created")
|
||||
|
||||
return kotlinPluginClassloader
|
||||
}
|
||||
protected abstract fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler): Plugin<Project>
|
||||
|
||||
private fun findSourceBuildScript(project: Project): ScriptHandler? {
|
||||
log.kotlinDebug("Looking for proper script handler")
|
||||
@@ -69,25 +55,24 @@ abstract class KotlinBasePluginWrapper: Plugin<Project> {
|
||||
return scriptHandler
|
||||
}
|
||||
log.kotlinDebug("not found, switching to parent")
|
||||
curProject = curProject.getParent()!!
|
||||
curProject = curProject.getParent() ?: break
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
open class KotlinPluginWrapper: KotlinBasePluginWrapper() {
|
||||
override fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler) = KotlinPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
|
||||
override fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler) = KotlinPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
|
||||
}
|
||||
|
||||
open class KotlinAndroidPluginWrapper : KotlinBasePluginWrapper() {
|
||||
override fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler) = KotlinAndroidPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
|
||||
override fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler) = KotlinAndroidPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
|
||||
}
|
||||
|
||||
open class Kotlin2JsPluginWrapper : KotlinBasePluginWrapper() {
|
||||
override fun getPlugin(pluginClassLoader: ParentLastURLClassLoader, scriptHandler: ScriptHandler) = Kotlin2JsPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
|
||||
override fun getPlugin(pluginClassLoader: ClassLoader, scriptHandler: ScriptHandler) = Kotlin2JsPlugin(scriptHandler, KotlinTasksProvider(pluginClassLoader))
|
||||
}
|
||||
|
||||
fun Logger.kotlinDebug(message: String) {
|
||||
this.debug("[KOTLIN] $message")
|
||||
}
|
||||
|
||||
|
||||
+39
-7
@@ -27,18 +27,43 @@ abstract class BaseGradleIT {
|
||||
deleteRecursively(workingDir)
|
||||
}
|
||||
|
||||
class BuildOptions(val withDaemon: Boolean = false)
|
||||
|
||||
class Project(val projectName: String, val wrapperVersion: String = "1.4", val minLogLevel: LogLevel = LogLevel.DEBUG)
|
||||
|
||||
class CompiledProject(val project: Project, val output: String, val resultCode: Int)
|
||||
|
||||
fun Project.build(vararg tasks: String, check: CompiledProject.() -> Unit) {
|
||||
fun Project.setupWorkingDir() {
|
||||
copyRecursively(File(resourcesRootFile, "testProject/$projectName"), workingDir)
|
||||
val projectDir = File(workingDir, projectName)
|
||||
copyDirRecursively(File(resourcesRootFile, "GradleWrapper-$wrapperVersion"), projectDir)
|
||||
val cmd = createCommand(tasks)
|
||||
copyDirRecursively(File(resourcesRootFile, "GradleWrapper-$wrapperVersion"), File(workingDir, projectName))
|
||||
}
|
||||
|
||||
fun Project.build(vararg tasks: String, options: BuildOptions = BuildOptions(), check: CompiledProject.() -> Unit) {
|
||||
val cmd = createBuildCommand(tasks, options)
|
||||
|
||||
println("<=== Test build: ${this.projectName} $cmd ===>")
|
||||
|
||||
runAndCheck(cmd, check)
|
||||
}
|
||||
|
||||
fun stopDaemon(ver: String) {
|
||||
val wrapperDir = File(resourcesRootFile, "GradleWrapper-$ver")
|
||||
val cmd = createGradleCommand(arrayListOf("-stop"))
|
||||
createProcess(cmd, wrapperDir)
|
||||
}
|
||||
|
||||
fun Project.stopDaemon(check: CompiledProject.() -> Unit) {
|
||||
val cmd = createGradleCommand(arrayListOf("-stop"))
|
||||
println("<=== Stop daemon: $cmd ===>")
|
||||
|
||||
runAndCheck(cmd, check)
|
||||
}
|
||||
|
||||
private fun Project.runAndCheck(cmd: List<String>, check: CompiledProject.() -> Unit) {
|
||||
val projectDir = File(workingDir, projectName)
|
||||
if (!projectDir.exists())
|
||||
setupWorkingDir()
|
||||
|
||||
val process = createProcess(cmd, projectDir)
|
||||
|
||||
val (output, resultCode) = readOutput(process)
|
||||
@@ -94,14 +119,21 @@ abstract class BaseGradleIT {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun Project.createCommand(params: Array<out String>): List<String> {
|
||||
private fun Project.createBuildCommand(params: Array<out String>, options: BuildOptions): List<String> {
|
||||
val pathToKotlinPlugin = "-PpathToKotlinPlugin=" + File("local-repo").getAbsolutePath()
|
||||
val tailParameters = params.asList() +
|
||||
listOf(pathToKotlinPlugin, "--no-daemon", "--${minLogLevel.name().toLowerCase()}", "-Pkotlin.gradle.test=true")
|
||||
listOf( pathToKotlinPlugin,
|
||||
if (options.withDaemon) "--daemon" else "--no-daemon",
|
||||
"--${minLogLevel.name().toLowerCase()}",
|
||||
"-Pkotlin.gradle.test=true")
|
||||
|
||||
return createGradleCommand(tailParameters)
|
||||
}
|
||||
|
||||
private fun createGradleCommand(tailParameters: List<String>): List<String> {
|
||||
return if (isWindows())
|
||||
listOf("cmd", "/C", "gradlew.bat") + tailParameters
|
||||
else
|
||||
else
|
||||
listOf("/bin/bash", "./gradlew") + tailParameters
|
||||
}
|
||||
|
||||
|
||||
+31
-2
@@ -1,7 +1,9 @@
|
||||
package org.jetbrains.kotlin.gradle
|
||||
|
||||
import org.junit.Test
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.jetbrains.kotlin.gradle.BaseGradleIT.Project
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
class KotlinGradleIT: BaseGradleIT() {
|
||||
|
||||
@@ -35,6 +37,34 @@ class KotlinGradleIT: BaseGradleIT() {
|
||||
}
|
||||
}
|
||||
|
||||
// This test isn't safe enough: gradle daemon is a singleton process (for a chosen version) and stopping it may
|
||||
// affect the build environment in an unpredictable way. Therefore it is now disabled.
|
||||
// TODO research the possibility to run isolated daemon build
|
||||
Ignore Test fun testKotlinOnlyDaemonMemory() {
|
||||
val project = Project("kotlinProject", "2.4")
|
||||
|
||||
project.stopDaemon {}
|
||||
|
||||
// build to "warm up" the daemon, if it is not started yet
|
||||
project.build("build", options = BaseGradleIT.BuildOptions(withDaemon = true)) {
|
||||
assertSuccessful()
|
||||
}
|
||||
|
||||
for (i in 1..3) {
|
||||
project.build("build", options = BaseGradleIT.BuildOptions(withDaemon = true)) {
|
||||
assertSuccessful()
|
||||
val matches = "\\[PERF\\] Used memory after build: (\\d+) kb \\(([+-]?\\d+) kb\\)".toRegex().match(output)
|
||||
assert(matches != null && matches.groups.size() == 3, "Used memory after build is not reported by plugin")
|
||||
val reportedGrowth = matches!!.groups.get(2)!!.value.toInt()
|
||||
assert(reportedGrowth <= 500, "Used memory growth $reportedGrowth > 500")
|
||||
}
|
||||
}
|
||||
|
||||
project.stopDaemon {
|
||||
assertSuccessful()
|
||||
}
|
||||
}
|
||||
|
||||
Test fun testKotlinClasspath() {
|
||||
Project("classpathTest", "1.6").build("build") {
|
||||
assertSuccessful()
|
||||
@@ -107,5 +137,4 @@ class KotlinGradleIT: BaseGradleIT() {
|
||||
assertFileExists("build/classes/main/example/AncestorClassGenerated.class")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
#Mon Aug 10 11:44:23 CEST 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
|
||||
Vendored
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
Reference in New Issue
Block a user