KotlinCompilationStep.kt
/*
* Copyright 2021 The Android Open Source Project
*
* 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 androidx.room.compiler.processing.util.compiler.steps
import androidx.room.compiler.processing.util.DiagnosticLocation
import androidx.room.compiler.processing.util.DiagnosticMessage
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compiler.SourceSet
import java.io.File
import javax.tools.Diagnostic
/**
* Kotlin compilation is run in multiple steps:
* process KSP
* process KAPT
* compile kotlin sources
* compile java sources
*
* Each step implements the [KotlinCompilationStep] interfaces and provides the arguments for
* the following step.
*/
internal interface KotlinCompilationStep {
/**
* A name to identify the step.
*/
val name: String
fun execute(
/**
* Temporary folder that can be used by the step
*/
workingDir: File,
/**
* Compilation parameters for the step.
*/
arguments: CompilationStepArguments
): CompilationStepResult
}
/**
* Diagnostic message that was captured from the compiler, before it is processed.
*/
internal data class RawDiagnosticMessage(
val kind: Diagnostic.Kind,
val message: String,
val location: Location?
) {
data class Location(
val path: String,
val line: Int,
)
}
/**
* Parameters for each compilation step
*/
internal data class CompilationStepArguments(
/**
* List of source sets. Each source set has a root folder that can be used to pass to the
* compiler.
*/
val sourceSets: List<SourceSet>,
/**
* Any additional classpath provided to the compilation
*/
val additionalClasspaths: List<File>,
/**
* If `true`, the classpath of the test application should be provided to the compiler
*/
val inheritClasspaths: Boolean,
/**
* Arguments to pass to the java compiler. This is also important for KAPT where part of the
* compilation is run by javac.
*/
val javacArguments: List<String>,
/**
* Arguments to pass to the kotlin compiler.
*/
val kotlincArguments: List<String>,
)
/**
* Result of a compilation step.
*/
internal data class CompilationStepResult(
/**
* Whether it succeeded or not.
*/
val success: Boolean,
/**
* List of source sets generated by this step
*/
val generatedSourceRoots: List<SourceSet>,
/**
* List of diagnotic messages created by this step
*/
val diagnostics: List<DiagnosticMessage>,
/**
* Arguments for the next compilation step. Current step might've modified its own parameters
* (e.g. add generated sources etc) for this one.
*/
val nextCompilerArguments: CompilationStepArguments,
/**
* If the step compiled sources, this field includes the list of Files for each classpath.
*/
val outputClasspath: List<File>
) {
val generatedSources: List<Source> by lazy {
generatedSourceRoots.flatMap { it.sources }
}
companion object {
/**
* Creates a [CompilationStepResult] that does not create any outputs but instead simply
* passes the arguments to the next step.
*/
fun skip(arguments: CompilationStepArguments) = CompilationStepResult(
success = true,
generatedSourceRoots = emptyList(),
diagnostics = emptyList(),
nextCompilerArguments = arguments,
outputClasspath = emptyList()
)
}
}
/**
* Associates [RawDiagnosticMessage]s with sources and creates [DiagnosticMessage]s.
*/
internal fun resolveDiagnostics(
diagnostics: List<RawDiagnosticMessage>,
sourceSets: List<SourceSet>,
): List<DiagnosticMessage> {
return diagnostics.map { rawDiagnostic ->
// match it to source
val location = rawDiagnostic.location
if (location == null) {
DiagnosticMessage(
kind = rawDiagnostic.kind,
msg = rawDiagnostic.message,
location = null,
)
} else {
// find matching source file
val source = sourceSets.firstNotNullOfOrNull {
it.findSourceFile(location.path)
}
// source might be null for KAPT if it failed to match the diagnostic to a real
// source file (e.g. error is reported on the stub)
check(source != null || location.path.contains("kapt")) {
"Cannot find source file for the diagnostic :/ $rawDiagnostic"
}
DiagnosticMessage(
kind = rawDiagnostic.kind,
msg = rawDiagnostic.message,
location = DiagnosticLocation(
source = source,
line = location.line,
),
)
}
}
}