diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/build.gradle.kts b/compiler/build-tools/kotlin-build-tools-api-tests/build.gradle.kts index eb76bfa6f9e..c729c01bafd 100644 --- a/compiler/build-tools/kotlin-build-tools-api-tests/build.gradle.kts +++ b/compiler/build-tools/kotlin-build-tools-api-tests/build.gradle.kts @@ -15,6 +15,8 @@ dependencies { kotlin { compilerOptions { optIn.add("org.jetbrains.kotlin.buildtools.api.ExperimentalBuildToolsApi") + optIn.add("kotlin.ExperimentalStdlibApi") + optIn.add("kotlin.io.path.ExperimentalPathApi") } } diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/AbstractModule.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/AbstractModule.kt new file mode 100644 index 00000000000..4fb6da5ad3d --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/AbstractModule.kt @@ -0,0 +1,39 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import java.nio.file.Path + +abstract class AbstractModule( + val project: Project, + override val moduleName: String, + val moduleDirectory: Path, + val dependencies: List, + override val additionalCompilationArguments: List = emptyList(), +) : Module { + override val sourcesDirectory: Path + get() = moduleDirectory.resolve("src") + + override val buildDirectory: Path + get() = moduleDirectory.resolve("build") + + override val outputDirectory: Path + get() = buildDirectory.resolve("output") + + override val location: Path + get() = outputDirectory + + override val icWorkingDir: Path + get() = buildDirectory.resolve("ic") + + override val snapshotFile: Path + get() = icWorkingDir.resolve("$moduleName.snapshot") + + override val icCachesDir: Path + get() = icWorkingDir.resolve("caches") + + override fun toString() = moduleName +} \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/BaseCompilationTest.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/BaseCompilationTest.kt new file mode 100644 index 00000000000..da7234e46e8 --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/BaseCompilationTest.kt @@ -0,0 +1,14 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path + +abstract class BaseCompilationTest { + @TempDir + lateinit var workingDirectory: Path +} \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/DefaultStrategyAgnosticCompilationTest.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/DefaultStrategyAgnosticCompilationTest.kt new file mode 100644 index 00000000000..a3b714bd928 --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/DefaultStrategyAgnosticCompilationTest.kt @@ -0,0 +1,18 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +@Retention(AnnotationRetention.RUNTIME) +@ParameterizedTest(name = "{0}: {displayName}") +@ArgumentsSource( + DefaultStrategyAgnosticCompilationTestArgumentProvider::class +) +annotation class DefaultStrategyAgnosticCompilationTest + diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/DefaultStrategyAgnosticCompilationTestArgumentProvider.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/DefaultStrategyAgnosticCompilationTestArgumentProvider.kt new file mode 100644 index 00000000000..4f3f39a6c29 --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/DefaultStrategyAgnosticCompilationTestArgumentProvider.kt @@ -0,0 +1,25 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import org.jetbrains.kotlin.buildtools.api.CompilationService +import org.junit.jupiter.api.Named.named +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import java.util.stream.Stream +import kotlin.streams.asStream + +class DefaultStrategyAgnosticCompilationTestArgumentProvider : ArgumentsProvider { + private val compilationService = CompilationService.loadImplementation(this.javaClass.classLoader) + + override fun provideArguments(context: ExtensionContext): Stream { + return sequenceOf( + named("in-process", compilationService.makeCompilerExecutionStrategyConfiguration().useInProcessStrategy()), + named("within daemon", compilationService.makeCompilerExecutionStrategyConfiguration().useDaemonStrategy(emptyList())), + ).map { Arguments.of(it) }.asStream() + } +} \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Dependency.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Dependency.kt new file mode 100644 index 00000000000..b0982e9253a --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Dependency.kt @@ -0,0 +1,13 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import java.nio.file.Path + +interface Dependency { + val location: Path + val snapshotFile: Path +} \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/JvmModule.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/JvmModule.kt new file mode 100644 index 00000000000..a434526f999 --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/JvmModule.kt @@ -0,0 +1,116 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import org.jetbrains.kotlin.buildtools.api.CompilationResult +import org.jetbrains.kotlin.buildtools.api.CompilationService +import org.jetbrains.kotlin.buildtools.api.CompilerExecutionStrategyConfiguration +import org.jetbrains.kotlin.buildtools.api.SourcesChanges +import org.jetbrains.kotlin.buildtools.api.jvm.AccessibleClassSnapshot +import org.jetbrains.kotlin.buildtools.api.jvm.ClassSnapshotGranularity +import org.jetbrains.kotlin.buildtools.api.jvm.ClasspathSnapshotBasedIncrementalCompilationApproachParameters +import org.jetbrains.kotlin.buildtools.api.jvm.JvmCompilationConfiguration +import org.jetbrains.kotlin.buildtools.api.tests.buildToolsVersion +import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion +import java.io.File +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.createParentDirectories +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.toPath + +class JvmModule( + project: Project, + moduleName: String, + moduleDirectory: Path, + dependencies: List, + additionalCompilationArguments: List = emptyList(), +) : AbstractModule( + project, + moduleName, + moduleDirectory, + dependencies, + additionalCompilationArguments, +) { + private val compilationService = CompilationService.loadImplementation(this.javaClass.classLoader) + + override fun compile( + strategyConfig: CompilerExecutionStrategyConfiguration, + compilationConfigAction: (JvmCompilationConfiguration) -> Unit, + ): CompilationResult { + val stdlibLocation = + KotlinVersion::class.java.protectionDomain.codeSource.location.toURI().toPath() // compile against the provided stdlib + val dependencyFiles = dependencies.map { it.location }.plusElement(stdlibLocation) + val compilationConfig = compilationService.makeJvmCompilationConfiguration() + compilationConfigAction(compilationConfig) + val defaultCompilationArguments = listOf( + "-no-reflect", + "-no-stdlib", + "-d", outputDirectory.absolutePathString(), + "-cp", dependencyFiles.joinToString(File.pathSeparator), + "-module-name", moduleName, + ) + return compilationService.compileJvm( + project.projectId, + strategyConfig, + compilationConfig, + sourcesDirectory.listDirectoryEntries().map { it.toFile() }, + defaultCompilationArguments + additionalCompilationArguments, + ) + } + + private fun generateClasspathSnapshot(dependency: Dependency): Path { + val snapshot = + compilationService.calculateClasspathSnapshot(dependency.location.toFile(), ClassSnapshotGranularity.CLASS_MEMBER_LEVEL) + val hash = snapshot.classSnapshots.values + .filterIsInstance() + .withIndex() + .sumOf { (index, snapshot) -> index * 31 + snapshot.classAbiHash } + // see details in docs for `CachedClasspathSnapshotSerializer` for details why we can't use a fixed name + val snapshotFile = icWorkingDir.resolve("dep-$hash.snapshot") + snapshotFile.createParentDirectories() + snapshot.saveSnapshot(snapshotFile.toFile()) + return snapshotFile + } + + override fun compileIncrementally( + strategyConfig: CompilerExecutionStrategyConfiguration, + sourcesChanges: SourcesChanges, + forceNonIncrementalCompilation: Boolean, + compilationConfigAction: (JvmCompilationConfiguration) -> Unit, + ): CompilationResult { + return compile(strategyConfig) { compilationConfig -> + val snapshots = dependencies.map { + generateClasspathSnapshot(it).toFile() + } + val shrunkClasspathSnapshotFile = icWorkingDir.resolve("shrunk-classpath-snapshot.bin") + val params = ClasspathSnapshotBasedIncrementalCompilationApproachParameters( + snapshots, + shrunkClasspathSnapshotFile.toFile() + ) + + val options = compilationConfig.makeClasspathSnapshotBasedIncrementalCompilationConfiguration() + options.setBuildDir(buildDirectory.toFile()) + options.setRootProjectDir(project.projectDirectory.toFile()) + + if (buildToolsVersion < KotlinToolingVersion(2, 0, 0, "Beta2")) { + // workaround for the incorrect default value + options.useOutputDirs(setOf(icCachesDir.toFile(), outputDirectory.toFile())) + } + + if (forceNonIncrementalCompilation) { + options.forceNonIncrementalMode() + } + + compilationConfig.useIncrementalCompilation( + icCachesDir.toFile(), + sourcesChanges, + params, + options, + ) + } + } +} \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Module.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Module.kt new file mode 100644 index 00000000000..bb3950a9579 --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Module.kt @@ -0,0 +1,34 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import org.jetbrains.kotlin.buildtools.api.CompilationResult +import org.jetbrains.kotlin.buildtools.api.CompilerExecutionStrategyConfiguration +import org.jetbrains.kotlin.buildtools.api.SourcesChanges +import org.jetbrains.kotlin.buildtools.api.jvm.JvmCompilationConfiguration +import java.nio.file.Path + +interface Module : Dependency { + val moduleName: String + val additionalCompilationArguments: List + val sourcesDirectory: Path + val buildDirectory: Path + val outputDirectory: Path + val icWorkingDir: Path + val icCachesDir: Path + + fun compile( + strategyConfig: CompilerExecutionStrategyConfiguration, + compilationConfigAction: (JvmCompilationConfiguration) -> Unit = {}, + ): CompilationResult + + fun compileIncrementally( + strategyConfig: CompilerExecutionStrategyConfiguration, + sourcesChanges: SourcesChanges, + forceNonIncrementalCompilation: Boolean = false, + compilationConfigAction: (JvmCompilationConfiguration) -> Unit = {}, + ): CompilationResult +} \ No newline at end of file diff --git a/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Project.kt b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Project.kt new file mode 100644 index 00000000000..e2fdb430c38 --- /dev/null +++ b/compiler/build-tools/kotlin-build-tools-api-tests/src/testCommon/kotlin/compilation/model/Project.kt @@ -0,0 +1,40 @@ +/* + * 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.buildtools.api.tests.compilation.model + +import org.jetbrains.kotlin.buildtools.api.ProjectId +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* +import kotlin.io.path.copyToRecursively +import kotlin.io.path.createDirectories +import kotlin.io.path.isDirectory + +class Project( + val projectDirectory: Path, +) { + val projectId = ProjectId.ProjectUUID(UUID.randomUUID()) + + fun module( + moduleName: String, + dependencies: List = emptyList(), + additionalCompilationArguments: List = emptyList(), + ): Module { + val moduleDirectory = projectDirectory.resolve(moduleName) + val module = JvmModule(this, moduleName, moduleDirectory, dependencies, additionalCompilationArguments) + module.sourcesDirectory.createDirectories() + val templatePath = Paths.get("src/testCommon/resources/modules/$moduleName") + assert(templatePath.isDirectory()) { + "Template for $moduleName not found. Expected template directory path is $templatePath" + } + templatePath.copyToRecursively(module.sourcesDirectory, followLinks = false) + return module + } +} + +fun BaseCompilationTest.project(action: Project.() -> Unit) { + Project(workingDirectory).action() +} \ No newline at end of file