KspCompilationStep.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.compiler.DiagnosticsMessageCollector
import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
import androidx.room.compiler.processing.util.compiler.TestKspRegistrar
import androidx.room.compiler.processing.util.compiler.toSourceSet
import com.google.devtools.ksp.KspOptions
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import org.jetbrains.kotlin.cli.common.ExitCode
import java.io.File

/**
 * Runs the Symbol Processors
 */
internal class KspCompilationStep(
    private val symbolProcessorProviders: List<SymbolProcessorProvider>,
    private val processorOptions: Map<String, String>
) : KotlinCompilationStep {
    override val name: String = "ksp"

    private fun createKspOptions(
        workingDir: File
    ): KspOptions.Builder {
        return KspOptions.Builder().apply {
            this.javaOutputDir = workingDir.resolve(JAVA_OUT_DIR)
            this.kotlinOutputDir = workingDir.resolve(KOTLIN_OUT_DIR)
            this.processingOptions.putAll(processorOptions)
        }
    }

    override fun execute(
        workingDir: File,
        arguments: CompilationStepArguments
    ): CompilationStepResult {
        if (symbolProcessorProviders.isEmpty()) {
            return CompilationStepResult.skip(arguments)
        }
        val kspMessages = DiagnosticsMessageCollector()
        val result = KotlinCliRunner.runKotlinCli(
            arguments = arguments,
            destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
            pluginRegistrars = listOf(
                TestKspRegistrar(
                    kspWorkingDir = workingDir.resolve("ksp-compiler"),
                    baseOptions = createKspOptions(workingDir),
                    processorProviders = symbolProcessorProviders,
                    messageCollector = kspMessages
                )
            ),
        )
        // workaround for https://github.com/google/ksp/issues/623
        val failureDueToWarnings = result.kotlinCliArguments.allWarningsAsErrors &&
            kspMessages.hasWarnings()

        val generatedSources = listOfNotNull(
            workingDir.resolve(KOTLIN_OUT_DIR).toSourceSet(),
            workingDir.resolve(JAVA_OUT_DIR).toSourceSet(),
        )
        val diagnostics = resolveDiagnostics(
            diagnostics = result.diagnostics + kspMessages.getDiagnostics(),
            sourceSets = arguments.sourceSets + generatedSources
        )
        return CompilationStepResult(
            success = result.exitCode == ExitCode.OK && !failureDueToWarnings,
            generatedSourceRoots = generatedSources,
            diagnostics = diagnostics,
            nextCompilerArguments = arguments.copy(
                sourceSets = arguments.sourceSets + generatedSources
            ),
            outputClasspath = listOf(result.compiledClasspath)
        )
    }

    companion object {
        private const val JAVA_OUT_DIR = "generatedJava"
        private const val KOTLIN_OUT_DIR = "generatedKotlin"
        private const val CLASS_OUT_FOLDER_NAME = "class-out"
    }
}