Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ idea {
}

dependencies {
testRuntime "org.jetbrains.kotlin:kotlin-scripting-compiler:1.3.50"

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testImplementation group: 'junit', name: 'junit', version: '4.12'

Expand All @@ -67,4 +69,4 @@ compileKotlin {
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs += ["-Xskip-runtime-version-check"]
}
}
20 changes: 18 additions & 2 deletions src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ class KotlinCompilation {
*/
var classpaths: List<File> = emptyList()

/**
* Paths to plugins to be made available in the compilation
*/
var pluginClasspaths: List<File> = emptyList()

/** Source files to be compiled */
var sources: List<SourceFile> = emptyList()

Expand Down Expand Up @@ -326,6 +331,8 @@ class KotlinCompilation {
it.destination = classesDir.absolutePath
it.classpath = commonClasspaths().joinToString(separator = File.pathSeparator)

it.pluginClasspaths = pluginClasspaths.map(File::getAbsolutePath).toTypedArray()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commonK2JVMArgs will be used in both steps: stubsAndApt and compileKotlin. So given compiler plugins will be run both times. Is this the desired behaviour? Unless the kapt plugin prevents the compiler from continuing with other plugins, like it short circuits the regular compilation. @Foso may know more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't have that problem because I didn't provide annotationProcessors, so stubsAndApt finished in the first conditional. Let's see the opinion by @Foso.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue still deserves some attention. Are we really sure that compiler plugins should be executed in both steps?


if(jdkHome != null) {
it.jdkHome = jdkHome!!.absolutePath
}
Expand Down Expand Up @@ -403,6 +410,13 @@ class KotlinCompilation {

/** Performs the 1st and 2nd compilation step to generate stubs and run annotation processors */
private fun stubsAndApt(sourceFiles: List<File>): ExitCode {
pluginClasspaths.forEach { filepath ->
if (!filepath.exists()) {
error("Plugin $filepath not found")
return ExitCode.INTERNAL_ERROR
}
}

Comment on lines +413 to +419
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should probably be moved to the calling function, where writing of the source files happens as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, which function? stubsAndApt is called from compile:

            // step 1 and 2: generate stubs and run annotation processors
            try {
                val exitCode = stubsAndApt(sourceFiles)
                if (exitCode != ExitCode.OK) {
                    val messages = internalMessageBuffer.readUtf8()
                    searchSystemOutForKnownErrors(messages)
                    return Result(exitCode, classesDir, messages)
                }
            } finally {
                KaptComponentRegistrar.threadLocalParameters.remove()
            }

And previously, the writing of the source files appears:

        // write given sources to working directory
        val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }

Though I don't find an error checking section like the one I found in stubsAndApt.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would just put it after val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) } in the compile function where all the initialization of directories is done. But you are right that it's kinda confusing where to put this stuff. There should be a clear separation between initialization and the different compile stages. Maybe I will rewrite it in the future.

if(annotationProcessors.isEmpty()) {
log("No services were given. Not running kapt steps.")
return ExitCode.OK
Expand Down Expand Up @@ -473,10 +487,12 @@ class KotlinCompilation {
}
}

if (pluginClasspaths.isNotEmpty())
warn("Included plugins in pluginsClasspaths will be executed twice.")

val k2JvmArgs = commonK2JVMArgs().also {
it.freeArgs = sourcePaths
it.pluginClasspaths = (it.pluginClasspaths?.toList() ?: emptyList<String>() + getResourcesPath())
.distinct().toTypedArray()
it.pluginClasspaths = (it.pluginClasspaths ?: emptyArray()) + arrayOf(getResourcesPath())
}

val compilerMessageCollector = PrintingMessageCollector(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.tschuchort.compiletesting

import com.tschuchort.compiletesting.KotlinCompilation.ExitCode
import io.github.classgraph.ClassGraph
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.fail
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.TypeElement
Expand Down Expand Up @@ -623,6 +625,28 @@ class KotlinCompilationTests {
assertClassLoadable(result, "${KotlinTestProcessor.GENERATED_PACKAGE}.${KotlinTestProcessor.GENERATED_JAVA_CLASS_NAME}")
}

@Test
fun `detects the plugin provided for compilation via pluginClasspaths property`() {
val result = defaultCompilerConfig().apply {
sources = listOf(SourceFile.kotlin("kSource.kt", "class KSource"))
pluginClasspaths = listOf(classpathOf("kotlin-scripting-compiler-1.3.50"))
}.compile()

assertThat(result.exitCode).isEqualTo(ExitCode.OK)
assertThat(result.messages).contains("provided plugin org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar")
}

@Test
fun `returns an internal error when adding a non existing plugin for compilation`() {
val result = defaultCompilerConfig().apply {
sources = listOf(SourceFile.kotlin("kSource.kt", "class KSource"))
pluginClasspaths = listOf(File("./non-existing-plugin.jar"))
}.compile()

assertThat(result.exitCode).isEqualTo(ExitCode.INTERNAL_ERROR)
assertThat(result.messages).contains("non-existing-plugin.jar not found")
}

private fun defaultCompilerConfig(): KotlinCompilation {
return KotlinCompilation().apply {
workingDir = temporaryFolder.root
Expand All @@ -645,6 +669,16 @@ class KotlinCompilationTests {
return fail<Nothing>("Class $className could not be loaded")
}
}

/**
* Returns the classpath for a dependency (format $name-$version).
* This is necessary to know the actual location of a dependency
* which has been included in test runtime (build.gradle).
*/
private fun classpathOf(dependency: String): File {
val regex = Regex(".*$dependency\\.jar")
return ClassGraph().classpathFiles.first { classpath -> classpath.name.matches(regex) }
}

class InheritedClass {}
}