Add flag to enable intra-project parallel tasks

To enable parallel tasks execution within a project,
add 'kotlin.parallel.tasks.in.project=true'
to gradle.properties or local.properties file .

    #KT-28155 fixed
This commit is contained in:
Alexey Tsvetkov
2018-11-06 23:08:39 +03:00
parent 4678a00324
commit 5fa627c501
12 changed files with 156 additions and 46 deletions
@@ -178,7 +178,8 @@ abstract class BaseGradleIT {
val kotlinDaemonDebugPort: Int? = null, val kotlinDaemonDebugPort: Int? = null,
val usePreciseJavaTracking: Boolean? = null, val usePreciseJavaTracking: Boolean? = null,
val withBuildCache: Boolean = false, val withBuildCache: Boolean = false,
val kaptOptions: KaptOptions? = null val kaptOptions: KaptOptions? = null,
val parallelTasksInProject: Boolean? = null
) )
data class KaptOptions(val verbose: Boolean, val useWorkers: Boolean) data class KaptOptions(val verbose: Boolean, val useWorkers: Boolean)
@@ -446,6 +447,18 @@ abstract class BaseGradleIT {
assertTasksUpToDate(tasks.toList()) assertTasksUpToDate(tasks.toList())
} }
fun CompiledProject.assertTasksSubmittedWork(vararg tasks: String) {
for (task in tasks) {
assertContains("Starting Kotlin compiler work from task '$task'")
}
}
fun CompiledProject.assertTasksDidNotSubmitWork(vararg tasks: String) {
for (task in tasks) {
assertNotContains("Starting Kotlin compiler work from task '$task'")
}
}
fun CompiledProject.getOutputForTask(taskName: String): String { fun CompiledProject.getOutputForTask(taskName: String): String {
val taskOutputRegex = ("\\[LIFECYCLE] \\[class org\\.gradle(?:\\.internal\\.buildevents)?\\.TaskExecutionLogger] :$taskName" + val taskOutputRegex = ("\\[LIFECYCLE] \\[class org\\.gradle(?:\\.internal\\.buildevents)?\\.TaskExecutionLogger] :$taskName" +
"([\\s\\S]+?)" + "([\\s\\S]+?)" +
@@ -567,6 +580,10 @@ abstract class BaseGradleIT {
add("-Pkapt.use.worker.api=${kaptOptions.useWorkers}") add("-Pkapt.use.worker.api=${kaptOptions.useWorkers}")
} }
options.parallelTasksInProject?.let {
add("-Pkotlin.parallel.tasks.in.project=$it")
}
// Workaround: override a console type set in the user machine gradle.properties (since Gradle 4.3): // Workaround: override a console type set in the user machine gradle.properties (since Gradle 4.3):
add("--console=plain") add("--console=plain")
@@ -249,6 +249,35 @@ class NewMultiplatformIT : BaseGradleIT() {
} }
} }
@Test
fun testParallelTasks() {
parallelTasksImpl(isParallel = true)
}
@Test
fun testNoParallelTasks() {
parallelTasksImpl(isParallel = false)
}
private fun parallelTasksImpl(isParallel: Boolean) = with(Project("new-mpp-parallel", gradleVersion)) {
val options = defaultBuildOptions().copy(parallelTasksInProject = isParallel)
build("assemble", options = options) {
assertSuccessful()
val tasks = arrayOf(":compileKotlinMetadata", ":compileKotlinJvm", ":compileKotlinJs")
if (isParallel) {
assertTasksSubmittedWork(*tasks)
} else {
assertTasksDidNotSubmitWork(*tasks)
}
val expectedKotlinOutputFiles = listOf(
kotlinClassesDir(sourceSet = "metadata/main") + "common/A.kotlin_metadata",
kotlinClassesDir(sourceSet = "jvm/main") + "common/A.class",
kotlinClassesDir(sourceSet = "js/main") + "new-mpp-parallel.js"
)
expectedKotlinOutputFiles.forEach { assertFileExists(it) }
}
}
@Test @Test
fun testLibWithTests() = with(Project("new-mpp-lib-with-tests", gradleVersion)) { fun testLibWithTests() = with(Project("new-mpp-lib-with-tests", gradleVersion)) {
build("check") { build("check") {
@@ -0,0 +1,37 @@
buildscript {
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin-multiplatform'
repositories {
mavenLocal()
jcenter()
}
kotlin {
sourceSets {
commonMain {}
jvmMain {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
}
}
jsMain {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
}
}
}
targets {
fromPreset(presets.jvm, 'jvm')
fromPreset(presets.js, 'js')
}
}
@@ -0,0 +1,9 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package common
class A(val x: Int)
@@ -5,16 +5,19 @@
package org.jetbrains.kotlin.compilerRunner package org.jetbrains.kotlin.compilerRunner
import org.gradle.api.Project import org.gradle.api.Task
import org.gradle.workers.ForkMode import org.gradle.workers.ForkMode
import org.gradle.workers.IsolationMode import org.gradle.workers.IsolationMode
import org.gradle.workers.WorkerExecutor import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
internal class GradleCompilerRunnerWithWorkers( internal class GradleCompilerRunnerWithWorkers(
project: Project, task: Task,
private val workersExecutor: WorkerExecutor private val workersExecutor: WorkerExecutor
) : GradleCompilerRunner(project) { ) : GradleCompilerRunner(task) {
override fun runCompilerAsync(workArgs: GradleKotlinCompilerWorkArguments) { override fun runCompilerAsync(workArgs: GradleKotlinCompilerWorkArguments) {
project.logger.kotlinDebug { "Starting Kotlin compiler work from task '${task.path}'" }
// todo: write tests with Workers enabled;
workersExecutor.submit(GradleKotlinCompilerWork::class.java) { config -> workersExecutor.submit(GradleKotlinCompilerWork::class.java) { config ->
config.isolationMode = IsolationMode.NONE config.isolationMode = IsolationMode.NONE
config.forkMode = ForkMode.NEVER config.forkMode = ForkMode.NEVER
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.compilerRunner package org.jetbrains.kotlin.compilerRunner
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.invocation.Gradle import org.gradle.api.invocation.Gradle
import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.jvm.tasks.Jar import org.gradle.jvm.tasks.Jar
@@ -50,7 +51,10 @@ const val COULD_NOT_CONNECT_TO_DAEMON_MESSAGE = "Could not connect to Kotlin com
internal fun kotlinCompilerExecutionStrategy(): String = internal fun kotlinCompilerExecutionStrategy(): String =
System.getProperty(KOTLIN_COMPILER_EXECUTION_STRATEGY_PROPERTY) ?: DAEMON_EXECUTION_STRATEGY System.getProperty(KOTLIN_COMPILER_EXECUTION_STRATEGY_PROPERTY) ?: DAEMON_EXECUTION_STRATEGY
internal open class GradleCompilerRunner(protected val project: Project) { internal open class GradleCompilerRunner(protected val task: Task) {
protected val project: Project
get() = task.project
/** /**
* Compiler might be executed asynchronuosly. Do not do anything requiring end of compilation after this function is called. * Compiler might be executed asynchronuosly. Do not do anything requiring end of compilation after this function is called.
* @see [GradleKotlinCompilerWork] * @see [GradleKotlinCompilerWork]
@@ -419,7 +419,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
kaptTask.destinationDir = getKaptIncrementalDataDir() kaptTask.destinationDir = getKaptIncrementalDataDir()
kaptTask.mapClasspath { kotlinCompile.classpath } kaptTask.mapClasspath { kotlinCompile.classpath }
kaptTask.generatedSourcesDir = sourcesOutputDir kaptTask.generatedSourcesDir = sourcesOutputDir
mapKotlinTaskProperties(project, kaptTask) PropertiesProvider(project).mapKotlinTaskProperties(kaptTask)
kaptTask.kaptClasspathConfigurations = kaptClasspathConfigurations kaptTask.kaptClasspathConfigurations = kaptClasspathConfigurations
buildAndAddOptionsTo(kaptTask, kaptTask.pluginOptions, aptMode = "stubs") buildAndAddOptionsTo(kaptTask, kaptTask.pluginOptions, aptMode = "stubs")
@@ -67,7 +67,7 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
throw GradleException("Could not find tools.jar in system classpath, which is required for kapt to work") throw GradleException("Could not find tools.jar in system classpath, which is required for kapt to work")
} }
val compilerRunner = GradleCompilerRunner(project) val compilerRunner = GradleCompilerRunner(this)
compilerRunner.runJvmCompilerAsync( compilerRunner.runJvmCompilerAsync(
sourcesToCompile = emptyList(), sourcesToCompile = emptyList(),
commonSources = emptyList(), commonSources = emptyList(),
@@ -23,21 +23,19 @@ import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.util.* import java.util.*
fun mapKotlinTaskProperties(project: Project, task: AbstractKotlinCompile<*>) { internal fun PropertiesProvider.mapKotlinTaskProperties(task: AbstractKotlinCompile<*>) {
PropertiesProvider(project).apply { coroutines?.let { task.coroutinesFromGradleProperties = it }
coroutines?.let { task.coroutinesFromGradleProperties = it } useFallbackCompilerSearch?.let { task.useFallbackCompilerSearch = it }
useFallbackCompilerSearch?.let { task.useFallbackCompilerSearch = it }
if (task is KotlinCompile) { if (task is KotlinCompile) {
incrementalJvm?.let { task.incremental = it } incrementalJvm?.let { task.incremental = it }
usePreciseJavaTracking?.let { usePreciseJavaTracking?.let {
task.usePreciseJavaTracking = it task.usePreciseJavaTracking = it
}
} }
}
if (task is Kotlin2JsCompile) { if (task is Kotlin2JsCompile) {
incrementalJs?.let { task.incremental = it } incrementalJs?.let { task.incremental = it }
}
} }
} }
@@ -71,6 +69,14 @@ internal class PropertiesProvider(private val project: Project) {
val useFallbackCompilerSearch: Boolean? val useFallbackCompilerSearch: Boolean?
get() = booleanProperty("kotlin.useFallbackCompilerSearch") get() = booleanProperty("kotlin.useFallbackCompilerSearch")
/**
* Enables parallel tasks execution within a project with Workers API.
* Does not enable using actual worker proccesses
* (Kotlin Daemon can be shared which uses less memory)
*/
val parallelTasksInProject: Boolean?
get() = booleanProperty("kotlin.parallel.tasks.in.project")
private fun booleanProperty(propName: String): Boolean? = private fun booleanProperty(propName: String): Boolean? =
property(propName)?.toBoolean() property(propName)?.toBoolean()
@@ -83,7 +83,7 @@ class KotlinMetadataTargetPreset(
get() = KotlinPlatformType.common get() = KotlinPlatformType.common
override fun buildCompilationProcessor(compilation: KotlinCommonCompilation): KotlinSourceSetProcessor<*> { override fun buildCompilationProcessor(compilation: KotlinCommonCompilation): KotlinSourceSetProcessor<*> {
val tasksProvider = KotlinTasksProvider(compilation.target.targetName, useWorkersForCompilation = true) val tasksProvider = KotlinTasksProvider(compilation.target.targetName)
return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion) return KotlinCommonSourceSetProcessor(project, compilation, tasksProvider, kotlinPluginVersion)
} }
@@ -129,7 +129,7 @@ class KotlinJvmTargetPreset(
get() = KotlinPlatformType.jvm get() = KotlinPlatformType.jvm
override fun buildCompilationProcessor(compilation: KotlinJvmCompilation): KotlinSourceSetProcessor<*> { override fun buildCompilationProcessor(compilation: KotlinJvmCompilation): KotlinSourceSetProcessor<*> {
val tasksProvider = KotlinTasksProvider(compilation.target.targetName, useWorkersForCompilation = true) val tasksProvider = KotlinTasksProvider(compilation.target.targetName)
return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) return Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion)
} }
@@ -158,7 +158,7 @@ class KotlinJsTargetPreset(
get() = KotlinPlatformType.js get() = KotlinPlatformType.js
override fun buildCompilationProcessor(compilation: KotlinJsCompilation): KotlinSourceSetProcessor<*> { override fun buildCompilationProcessor(compilation: KotlinJsCompilation): KotlinSourceSetProcessor<*> {
val tasksProvider = KotlinTasksProvider(compilation.target.targetName, useWorkersForCompilation = true) val tasksProvider = KotlinTasksProvider(compilation.target.targetName)
return Kotlin2JsSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion) return Kotlin2JsSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion)
} }
@@ -180,7 +180,7 @@ class KotlinAndroidTargetPreset(
} }
KotlinAndroidPlugin.applyToTarget( KotlinAndroidPlugin.applyToTarget(
project, result, AndroidTasksProvider(name, useWorkersForCompilation = true), project, result, AndroidTasksProvider(name),
kotlinPluginVersion kotlinPluginVersion
) )
@@ -207,8 +207,7 @@ class KotlinJvmWithJavaTargetPreset(
} }
AbstractKotlinPlugin.configureTarget(target) { compilation -> AbstractKotlinPlugin.configureTarget(target) { compilation ->
val tasksProvider = KotlinTasksProvider(name, useWorkersForCompilation = true) Kotlin2JvmSourceSetProcessor(project, KotlinTasksProvider(name), compilation, kotlinPluginVersion)
Kotlin2JvmSourceSetProcessor(project, tasksProvider, compilation, kotlinPluginVersion)
} }
target.compilations.all { compilation -> target.compilations.all { compilation ->
@@ -250,7 +250,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
} }
internal open fun compilerRunner(): GradleCompilerRunner = internal open fun compilerRunner(): GradleCompilerRunner =
GradleCompilerRunner(project) GradleCompilerRunner(this)
override fun compile() { override fun compile() {
assert(false, { "unexpected call to compile()" }) assert(false, { "unexpected call to compile()" })
@@ -456,21 +456,21 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
internal open class KotlinCompileWithWorkers @Inject constructor( internal open class KotlinCompileWithWorkers @Inject constructor(
@Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor @Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor
) : KotlinCompile() { ) : KotlinCompile() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(project, workerExecutor) override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
} }
@CacheableTask @CacheableTask
internal open class Kotlin2JsCompileWithWorkers @Inject constructor( internal open class Kotlin2JsCompileWithWorkers @Inject constructor(
@Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor @Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor
) : Kotlin2JsCompile() { ) : Kotlin2JsCompile() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(project, workerExecutor) override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
} }
@CacheableTask @CacheableTask
internal open class KotlinCompileCommonWithWorkers @Inject constructor( internal open class KotlinCompileCommonWithWorkers @Inject constructor(
@Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor @Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor
) : KotlinCompileCommon() { ) : KotlinCompileCommon() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(project, workerExecutor) override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
} }
@CacheableTask @CacheableTask
@@ -22,44 +22,45 @@ import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.defaultSourceSetName import org.jetbrains.kotlin.gradle.plugin.mpp.defaultSourceSetName
import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask
internal open class KotlinTasksProvider( internal open class KotlinTasksProvider(val targetName: String) {
val targetName: String,
private val useWorkersForCompilation: Boolean = false
) {
open fun createKotlinJVMTask( open fun createKotlinJVMTask(
project: Project, project: Project,
name: String, name: String,
compilation: KotlinCompilation compilation: KotlinCompilation
): KotlinCompile { ): KotlinCompile {
val taskClass = if (useWorkersForCompilation) KotlinCompileWithWorkers::class.java else KotlinCompile::class.java val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<KotlinCompile, KotlinCompileWithWorkers>(properties)
return project.tasks.create(name, taskClass).apply { return project.tasks.create(name, taskClass).apply {
configure(this, project, compilation) configure(this, project, properties, compilation)
} }
} }
fun createKotlinJSTask(project: Project, name: String, compilation: KotlinCompilation): Kotlin2JsCompile { fun createKotlinJSTask(project: Project, name: String, compilation: KotlinCompilation): Kotlin2JsCompile {
val taskClass = if (useWorkersForCompilation) Kotlin2JsCompileWithWorkers::class.java else Kotlin2JsCompile::class.java val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<Kotlin2JsCompile, Kotlin2JsCompileWithWorkers>(properties)
return project.tasks.create(name, taskClass).apply { return project.tasks.create(name, taskClass).apply {
configure(this, project, compilation) configure(this, project, properties, compilation)
} }
} }
fun createKotlinCommonTask(project: Project, name: String, compilation: KotlinCompilation): KotlinCompileCommon { fun createKotlinCommonTask(project: Project, name: String, compilation: KotlinCompilation): KotlinCompileCommon {
val taskClass = if (useWorkersForCompilation) KotlinCompileCommonWithWorkers::class.java else KotlinCompileCommon::class.java val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<KotlinCompileCommon, KotlinCompileCommonWithWorkers>(properties)
return project.tasks.create(name, taskClass).apply { return project.tasks.create(name, taskClass).apply {
configure(this, project, compilation) configure(this, project, properties, compilation)
} }
} }
open fun configure( open fun configure(
kotlinTask: AbstractKotlinCompile<*>, kotlinTask: AbstractKotlinCompile<*>,
project: Project, project: Project,
propertiesProvider: PropertiesProvider,
compilation: KotlinCompilation compilation: KotlinCompilation
) { ) {
kotlinTask.sourceSetName = compilation.name kotlinTask.sourceSetName = compilation.name
kotlinTask.friendTaskName = taskToFriendTaskMapper[kotlinTask] kotlinTask.friendTaskName = taskToFriendTaskMapper[kotlinTask]
mapKotlinTaskProperties(project, kotlinTask) propertiesProvider.mapKotlinTaskProperties(kotlinTask)
project.whenEvaluated { project.whenEvaluated {
val languageSettings = project.kotlinExtension.sourceSets.findByName(compilation.defaultSourceSetName)?.languageSettings val languageSettings = project.kotlinExtension.sourceSets.findByName(compilation.defaultSourceSetName)?.languageSettings
@@ -72,17 +73,22 @@ internal open class KotlinTasksProvider(
protected open val taskToFriendTaskMapper: TaskToFriendTaskMapper = protected open val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.Default(targetName) RegexTaskToFriendTaskMapper.Default(targetName)
private inline fun <reified Task, reified WorkersTask : Task> taskOrWorkersTask(properties: PropertiesProvider): Class<out Task> =
if (properties.parallelTasksInProject != true) Task::class.java else WorkersTask::class.java
} }
internal class AndroidTasksProvider( internal class AndroidTasksProvider(targetName: String) : KotlinTasksProvider(targetName) {
targetName: String,
useWorkersForCompilation: Boolean = false
) : KotlinTasksProvider(targetName, useWorkersForCompilation = useWorkersForCompilation) {
override val taskToFriendTaskMapper: TaskToFriendTaskMapper = override val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.Android(targetName) RegexTaskToFriendTaskMapper.Android(targetName)
override fun configure(kotlinTask: AbstractKotlinCompile<*>, project: Project, compilation: KotlinCompilation) { override fun configure(
super.configure(kotlinTask, project, compilation) kotlinTask: AbstractKotlinCompile<*>,
project: Project,
propertiesProvider: PropertiesProvider,
compilation: KotlinCompilation
) {
super.configure(kotlinTask, project, propertiesProvider, compilation)
kotlinTask.useModuleDetection = true kotlinTask.useModuleDetection = true
} }
} }