-
Notifications
You must be signed in to change notification settings - Fork 5.7k
/
AbstractKotlinCompile.kt
349 lines (301 loc) · 14.5 KB
/
AbstractKotlinCompile.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.file.*
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.work.*
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.build.report.metrics.*
import org.jetbrains.kotlin.buildtools.api.SourcesChanges
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.compilerRunner.*
import org.jetbrains.kotlin.compilerRunner.CompilerExecutionSettings
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.compilerRunner.UsesCompilerSystemPropertiesService
import org.jetbrains.kotlin.compilerRunner.createGradleCompilerRunner
import org.jetbrains.kotlin.daemon.common.MultiModuleICSettings
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions
import org.jetbrains.kotlin.gradle.incremental.UsesIncrementalModuleInfoBuildService
import org.jetbrains.kotlin.gradle.internal.UsesClassLoadersCachingBuildService
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.UsesBuildFinishedListenerService
import org.jetbrains.kotlin.gradle.plugin.UsesVariantImplementationFactories
import org.jetbrains.kotlin.gradle.plugin.diagnostics.UsesKotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.internal.UsesBuildIdProviderService
import org.jetbrains.kotlin.gradle.plugin.statistics.CompileKotlinTaskMetrics
import org.jetbrains.kotlin.gradle.plugin.statistics.UsesBuildFusService
import org.jetbrains.kotlin.gradle.report.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.IncrementalCompilationFeatures
import java.io.File
import javax.inject.Inject
import org.jetbrains.kotlin.gradle.tasks.cleanOutputsAndLocalState as cleanOutputsAndLocalStateUtil
private const val ABI_SNAPSHOT_FILE_NAME = "abi-snapshot.bin"
@DisableCachingByDefault(because = "Abstract super-class, not to be instantiated directly")
abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constructor(
objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor,
) : AbstractKotlinCompileTool<T>(objectFactory),
CompileUsingKotlinDaemonWithNormalization,
UsesBuildMetricsService,
UsesIncrementalModuleInfoBuildService,
UsesCompilerSystemPropertiesService,
UsesVariantImplementationFactories,
UsesBuildFinishedListenerService,
UsesClassLoadersCachingBuildService,
UsesKotlinToolingDiagnostics,
UsesBuildIdProviderService,
UsesBuildFusService,
BaseKotlinCompile {
init {
cacheOnlyIfEnabledForKotlin()
}
@get:Inject
internal abstract val projectLayout: ProjectLayout
@get:Inject
internal abstract val fileSystemOperations: FileSystemOperations
// avoid creating directory in getter: this can lead to failure in parallel build
@get:OutputDirectory
internal open val taskBuildCacheableOutputDirectory: DirectoryProperty = objectFactory.directoryProperty()
// avoid creating directory in getter: this can lead to failure in parallel build
@get:LocalState
internal abstract val taskBuildLocalStateDirectory: DirectoryProperty
@get:Nested
abstract val compilerOptions: KotlinCommonCompilerOptions
@get:Internal
internal val buildHistoryFile
get() = taskBuildLocalStateDirectory.file("build-history.bin")
// indicates that task should compile kotlin incrementally if possible
// it's not possible when IncrementalTaskInputs#isIncremental returns false (i.e first build)
// todo: deprecate and remove (we may need to design api for configuring IC)
// don't rely on it to check if IC is enabled, use isIncrementalCompilationEnabled instead
@get:Internal
var incremental: Boolean = false
set(value) {
field = value
logger.kotlinDebug { "Set $this.incremental=$value" }
}
@Input
internal open fun isIncrementalCompilationEnabled(): Boolean =
incremental
// This allows us to treat friendPaths as Input rather than InputFiles
@get:Input
internal val friendPathsSet: Provider<Set<String>>
get() {
val buildDirFile = projectLayout.buildDirectory.asFile.get()
return friendPaths.elements.map { providedSet ->
providedSet
.map { it.asFile }
.filter { it.exists() }
.map { it.normalize().relativeTo(buildDirFile).invariantSeparatorsPath }
.toSet()
}
}
@get:Input
@get:Optional
abstract val explicitApiMode: Property<ExplicitApiMode>
@get:Internal
internal abstract val suppressKotlinOptionsFreeArgsModificationWarning: Property<Boolean>
internal fun reportingSettings() = buildMetricsService.orNull?.parameters?.reportingSettings?.orNull ?: ReportingSettings()
@get:Internal
protected val multiModuleICSettings: MultiModuleICSettings
get() = MultiModuleICSettings(buildHistoryFile.get().asFile, useModuleDetection.get())
/**
* Plugin Data provided by [KpmCompilerPlugin]
*/
@get:Optional
@get:Nested
// TODO: replace with objects.property and introduce task configurator
internal var kotlinPluginData: Provider<KotlinCompilerPluginData>? = null
@get:Internal
internal val javaOutputDir: DirectoryProperty = objectFactory.directoryProperty()
@get:InputFiles
@get:IgnoreEmptyDirectories
@get:Incremental
@get:NormalizeLineEndings
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val commonSourceSet: ConfigurableFileCollection = objectFactory.fileCollection()
@get:Internal
val abiSnapshotFile
get() = taskBuildCacheableOutputDirectory.file(ABI_SNAPSHOT_FILE_NAME)
@get:Input
val abiSnapshotRelativePath: Property<String> = objectFactory.property(String::class.java).value(
//TODO update to support any jar changes
"$name/${ABI_SNAPSHOT_FILE_NAME}"
)
@get:Internal
internal val friendSourceSets = objectFactory.listProperty(String::class.java)
@get:Internal
internal abstract val kotlinCompilerArgumentsLogLevel: Property<KotlinCompilerArgumentsLogLevel>
@get:Internal
protected val gradleCompileTaskProvider: Provider<GradleCompileTaskProvider> = objectFactory
.property(
objectFactory.newInstance<GradleCompileTaskProvider>(this, project, incrementalModuleInfoProvider)
)
@get:Internal
internal open val defaultKotlinJavaToolchain: Provider<DefaultKotlinJavaToolchain> = objectFactory
.propertyWithNewInstance({ null })
@get:Internal
internal val compilerRunner: Provider<GradleCompilerRunner> =
objectFactory.propertyWithConvention(
gradleCompileTaskProvider.flatMap { taskProvider ->
compilerExecutionStrategy
.zip(metrics) { executionStrategy, metrics ->
metrics to executionStrategy
}
.flatMap { params ->
defaultKotlinJavaToolchain
.map {
val toolsJar = it.currentJvmJdkToolsJar.orNull
createGradleCompilerRunner(
taskProvider,
toolsJar,
CompilerExecutionSettings(
normalizedKotlinDaemonJvmArguments.orNull,
params.second,
useDaemonFallbackStrategy.get()
),
params.first,
workerExecutor,
runViaBuildToolsApi.get(),
classLoadersCachingService,
buildFinishedListenerService,
buildIdService,
buildFusService.orNull?.getFusMetricsConsumer()
)
}
}
}
)
@get:Internal
internal abstract val preciseCompilationResultsBackup: Property<Boolean>
@get:Internal
internal abstract val keepIncrementalCompilationCachesInMemory: Property<Boolean>
/** See [org.jetbrains.kotlin.incremental.IncrementalCompilationFeatures.enableUnsafeIncrementalCompilationForMultiplatform] */
@get:Internal
internal abstract val enableUnsafeIncrementalCompilationForMultiplatform: Property<Boolean>
/** Task outputs that we don't want to include in [TaskOutputsBackup] (see [TaskOutputsBackup.outputsToRestore] for more info). */
@get:Internal
internal abstract val taskOutputsBackupExcludes: SetProperty<File>
@TaskAction
fun execute(inputChanges: InputChanges) {
val buildMetrics = metrics.get()
buildMetrics.addTimeMetric(GradleBuildPerformanceMetric.START_TASK_ACTION_EXECUTION)
buildMetrics.measure(GradleBuildTime.OUT_OF_WORKER_TASK_ACTION) {
buildFusService.orNull?.reportFusMetrics {
CompileKotlinTaskMetrics.collectMetrics(name, compilerOptions, it)
}
validateCompilerClasspath()
systemPropertiesService.get().startIntercept()
CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value = "true"
// If task throws exception, but its outputs are changed during execution,
// then Gradle forces next build to be non-incremental (see Gradle's DefaultTaskArtifactStateRepository#persistNewOutputs)
// To prevent this, we backup outputs before incremental build and restore when exception is thrown
val outputsBackup: TaskOutputsBackup? =
if (isIncrementalCompilationEnabled() && inputChanges.isIncremental)
buildMetrics.measure(GradleBuildTime.BACKUP_OUTPUT) {
TaskOutputsBackup(
fileSystemOperations,
projectLayout.buildDirectory,
projectLayout.buildDirectory.dir("snapshot/kotlin/$name"),
outputsToRestore = allOutputFiles() - taskOutputsBackupExcludes.get(),
GradleKotlinLogger(logger),
).also {
it.createSnapshot()
}
}
else null
if (!isIncrementalCompilationEnabled()) {
cleanOutputsAndLocalState("IC is disabled")
} else if (!inputChanges.isIncremental) {
cleanOutputsAndLocalState("Task cannot run incrementally")
}
executeImpl(inputChanges, outputsBackup)
}
buildMetricsService.orNull?.also { it.addTask(path, this.javaClass, buildMetrics) }
}
protected open fun cleanOutputsAndLocalState(reason: String?) {
cleanOutputsAndLocalStateUtil(reason)
}
protected open fun skipCondition(): Boolean = sources.isEmpty
@get:Internal
protected open val incrementalProps: List<FileCollection>
get() = listOfNotNull(
sources,
libraries,
commonSourceSet
)
/**
* Entry point for getting IC feature toggles in Gradle. Child classes should override it
* if they have a platform-specific Input.
*/
protected open fun makeIncrementalCompilationFeatures(): IncrementalCompilationFeatures {
return IncrementalCompilationFeatures(
preciseCompilationResultsBackup = preciseCompilationResultsBackup.get(),
keepIncrementalCompilationCachesInMemory = keepIncrementalCompilationCachesInMemory.get(),
enableUnsafeIncrementalCompilationForMultiplatform = enableUnsafeIncrementalCompilationForMultiplatform.get(),
)
}
private fun executeImpl(
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
val allKotlinSources = sources.asFileTree.files
logger.kotlinDebug { "All kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(gradleCompileTaskProvider.get().projectDir.get())}" }
if (!inputChanges.isIncremental && skipCondition()) {
// Skip running only if non-incremental run. Otherwise, we may need to do some cleanup.
logger.kotlinDebug { "No Kotlin files found, skipping Kotlin compiler task" }
return
}
val args = createCompilerArguments()
taskBuildCacheableOutputDirectory.get().asFile.mkdirs()
taskBuildLocalStateDirectory.get().asFile.mkdirs()
callCompilerAsync(
args,
inputChanges,
taskOutputsBackup
)
}
protected fun getChangedFiles(
inputChanges: InputChanges,
incrementalProps: List<FileCollection>,
filter: (File) -> Boolean = { true },
) = if (!inputChanges.isIncremental) {
SourcesChanges.Unknown
} else {
incrementalProps
.fold(mutableListOf<File>() to mutableListOf<File>()) { (modified, removed), prop ->
inputChanges.getFileChanges(prop).forEach {
if (!filter(it.file)) return@forEach
when (it.changeType) {
ChangeType.ADDED, ChangeType.MODIFIED -> modified.add(it.file)
ChangeType.REMOVED -> removed.add(it.file)
else -> Unit
}
}
modified to removed
}
.run {
SourcesChanges.Known(first, second)
}
}
/**
* Compiler might be executed asynchronously. Do not do anything requiring end of compilation after this function is called.
* @see [GradleKotlinCompilerWork]
*/
internal abstract fun callCompilerAsync(
args: T,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
)
}