Merge pull request #739 from JetBrains/rr/gradle-plugin-classloading-M13-2

merging gradle plugin classloading changes
This commit is contained in:
Ilya Chernikov
2015-08-11 17:26:11 +02:00
15 changed files with 575 additions and 243 deletions
-1
View File
@@ -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>
+1
View File
@@ -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>
@@ -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>
@@ -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,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")
}
@@ -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
}
@@ -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")
}
}
}
@@ -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
@@ -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 "$@"
@@ -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