diff --git a/kover-gradle-plugin/build.gradle.kts b/kover-gradle-plugin/build.gradle.kts index 6f41ed3c..64bb4a46 100644 --- a/kover-gradle-plugin/build.gradle.kts +++ b/kover-gradle-plugin/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import java.time.LocalDate import java.time.format.DateTimeFormatter import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation @@ -70,6 +72,7 @@ val functionalTest by tasks.registering(Test::class) { setSystemPropertyFromProject("kover.test.kotlin.version") systemProperties["kotlinVersion"] = embeddedKotlinVersion + systemProperties["gradleVersion"] = gradle.gradleVersion systemProperties["koverVersion"] = version systemProperties["localRepositoryPath"] = localRepositoryUri.path @@ -125,19 +128,23 @@ fun Test.setBooleanSystemPropertyFromProject( tasks.check { dependsOn(functionalTest) } -tasks.withType().configureEach { - kotlinOptions { - allWarningsAsErrors = true - jvmTarget = "1.8" - - // Kover works with the stdlib of at least version `1.4.x` - languageVersion = "1.4" - apiVersion = "1.4" - // Kotlin compiler 1.7 issues a warning if `languageVersion` or `apiVersion` 1.4 is used - suppress it - freeCompilerArgs = freeCompilerArgs + "-Xsuppress-version-warnings" +afterEvaluate { + // Workaround: + // `kotlin-dsl` itself specifies the language version to ensure compatibility of the Kotlin DSL API + // Since we ourselves guarantee and test compatibility with previous Gradle versions, we can override language version + // The easiest way to do this now is to specify the version in the `afterEvaluate` block + tasks.withType().configureEach { + compilerOptions { + allWarningsAsErrors.set(true) + jvmTarget.set(JvmTarget.JVM_1_8) + languageVersion.set(KotlinVersion.KOTLIN_1_5) + apiVersion.set(KotlinVersion.KOTLIN_1_5) + freeCompilerArgs.add("-Xsuppress-version-warnings") + } } } + tasks.dokkaHtml { moduleName.set("Kover Gradle Plugin") outputDirectory.set(rootProject.layout.projectDirectory.dir("docs/gradle-plugin/dokka").asFile) diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/MetadataCompatibilityTests.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/MetadataCompatibilityTests.kt new file mode 100644 index 00000000..5509f788 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/MetadataCompatibilityTests.kt @@ -0,0 +1,12 @@ +package kotlinx.kover.gradle.plugin.test.functional.cases + +import kotlinx.kover.gradle.plugin.test.functional.framework.checker.CheckerContext +import kotlinx.kover.gradle.plugin.test.functional.framework.starter.TemplateTest + +internal class MetadataCompatibilityTests { + + @TemplateTest("buildsrc-usage", [":koverXmlReport"]) + fun CheckerContext.test() { + // no-op + } +} \ No newline at end of file diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/common/Environment.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/common/Environment.kt index 0cca808f..ca325ab1 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/common/Environment.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/common/Environment.kt @@ -32,7 +32,7 @@ internal val overriddenKotlinVersion = System.getProperty("kover.test.kotlin.ver /** * Custom version of Gradle runner for functional tests. */ -internal val overriddenGradleWrapperVersion: String? = System.getProperty("kover.test.gradle.version") +internal val overriddenGradleVersion: String? = System.getProperty("kover.test.gradle.version") /** * Result path to the Android SDK. `null` if not defined. @@ -40,6 +40,11 @@ internal val overriddenGradleWrapperVersion: String? = System.getProperty("kover internal val androidSdkDir: String? = System.getProperty("kover.test.android.sdk") +/** + * Version of gradle which functional tests are run by. + */ +internal val defaultGradleVersion: String = System.getProperty("gradleVersion") + ?: throw Exception("System property 'gradleVersion' not defined for functional tests") /** * Flag to run functional tests within debug agent. diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/BuildsRunner.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/BuildsRunner.kt index ad4090be..2b26508e 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/BuildsRunner.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/BuildsRunner.kt @@ -7,7 +7,11 @@ package kotlinx.kover.gradle.plugin.test.functional.framework.runner import kotlinx.kover.gradle.plugin.test.functional.framework.common.isDebugEnabled import kotlinx.kover.gradle.plugin.test.functional.framework.common.logInfo import kotlinx.kover.gradle.plugin.test.functional.framework.common.uri +import kotlinx.kover.gradle.plugin.test.functional.framework.starter.* +import kotlinx.kover.gradle.plugin.test.functional.framework.starter.buildSrc import kotlinx.kover.gradle.plugin.test.functional.framework.starter.patchSettingsFile +import kotlinx.kover.gradle.plugin.util.SemVer +import org.opentest4j.TestAbortedException import java.io.File import java.nio.file.Files @@ -39,6 +43,7 @@ internal interface GradleBuild { } internal data class BuildEnv( + val gradleVersion: SemVer, val wrapperDir: File, val androidSdkDir: String? = null, val disableBuildCacheByDefault: Boolean = true @@ -75,11 +80,15 @@ private class BuildSourceImpl(val localMavenDir: String, val koverVersion: Strin actualDir } - targetDir.patchSettingsFile( + targetDir.settings.patchSettingsFile( "$buildType '$buildName', project dir: ${targetDir.uri}", koverVersion, localMavenDir, overriddenKotlinVersion ) + val buildSrcScript = targetDir.buildSrc?.build + buildSrcScript?.patchKoverDependency(koverVersion) + buildSrcScript?.addLocalRepository(localMavenDir) + return GradleBuildImpl(targetDir, copy, buildName, buildType) } } @@ -94,6 +103,18 @@ private class GradleBuildImpl( private var runCount = 0 override fun run(args: List, env: BuildEnv): BuildResult { + val requirements = targetDir.requirements + requirements.minGradle?.let { minVersion -> + if (env.gradleVersion < minVersion) { + throw TestAbortedException("Used Gradle version '${env.gradleVersion}' lower than minimal required '$minVersion'") + } + } + requirements.maxGradle?.let { maxVersion -> + if (env.gradleVersion >= maxVersion) { + throw TestAbortedException("Used Gradle version '${env.gradleVersion}' higher or equals than maximal '$maxVersion'") + } + } + logInfo("Starting build $buildType '$buildName' with commands '${args.joinToString(" ")}'") val gradleArgs: MutableList = mutableListOf() diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/CustomizableRunning.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/CustomizableRunning.kt index a1e8f0ae..d4f7085c 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/CustomizableRunning.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/CustomizableRunning.kt @@ -9,10 +9,12 @@ import kotlinx.kover.gradle.plugin.test.functional.framework.common.androidSdkDi import kotlinx.kover.gradle.plugin.test.functional.framework.common.koverVersionCurrent import kotlinx.kover.gradle.plugin.test.functional.framework.common.defaultGradleWrapperDir import kotlinx.kover.gradle.plugin.test.functional.framework.common.examplesDir -import kotlinx.kover.gradle.plugin.test.functional.framework.common.overriddenGradleWrapperVersion +import kotlinx.kover.gradle.plugin.test.functional.framework.common.overriddenGradleVersion import kotlinx.kover.gradle.plugin.test.functional.framework.common.localRepositoryPath import kotlinx.kover.gradle.plugin.test.functional.framework.common.overriddenKotlinVersion import kotlinx.kover.gradle.plugin.test.functional.framework.common.templateBuildsDir +import kotlinx.kover.gradle.plugin.util.SemVer +import org.gradle.kotlin.dsl.provideDelegate import java.io.File import java.nio.file.Files @@ -57,20 +59,27 @@ internal fun generateBuild(generator: (File) -> Unit): BuildSource { } -internal fun GradleBuild.runWithParams(args: List): BuildResult { - val wrapperDir = - if (overriddenGradleWrapperVersion == null) defaultGradleWrapperDir else getWrapper(overriddenGradleWrapperVersion) +internal fun GradleBuild.runWithParams(vararg args: String): BuildResult { + return runWithParams(args.toList()) +} - val buildEnv = BuildEnv(wrapperDir, androidSdkDir) +internal fun GradleBuild.runWithParams(args: List): BuildResult { + val buildEnv = BuildEnv(gradleVersion, wrapperDir, androidSdkDir) return run(args, buildEnv) } -internal fun GradleBuild.runWithParams(vararg args: String): BuildResult { - return runWithParams(args.toList()) + +private val gradleVersion: SemVer by lazy { + val version = overriddenGradleVersion ?: defaultGradleVersion + SemVer.ofVariableOrNull(version) ?: throw IllegalArgumentException("Can not parse Gradle version '$version'") } -private fun getWrapper(version: String): File { - val wrapperDir = gradleWrappersRoot.resolve(version) - if (!wrapperDir.exists()) throw Exception("Wrapper for Gradle version '$version' is not supported by functional tests") + +private val wrapperDir = + if (overriddenGradleVersion == null) defaultGradleWrapperDir else getWrapper(overriddenGradleVersion) + +private fun getWrapper(gradleVersion: String): File { + val wrapperDir = gradleWrappersRoot.resolve(gradleVersion) + if (!wrapperDir.exists()) throw Exception("Wrapper for Gradle version '$gradleVersion' is not supported by functional tests") return wrapperDir } diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt index e99da684..32207a4d 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt @@ -12,6 +12,7 @@ import kotlinx.kover.gradle.plugin.test.functional.framework.runner.BuildSource import kotlinx.kover.gradle.plugin.test.functional.framework.runner.GradleBuild import kotlinx.kover.gradle.plugin.test.functional.framework.runner.runWithParams import kotlinx.kover.gradle.plugin.test.functional.framework.writer.* +import kotlinx.kover.gradle.plugin.util.SemVer import org.junit.jupiter.api.extension.* import java.io.* import java.lang.reflect.AnnotatedElement @@ -73,6 +74,40 @@ internal abstract class DirectoryBasedGradleTest : BeforeTestExecutionCallback, } } +internal val File.requirements: BuildRequirements + get() { + val file = resolve("requires") + if (!(file.exists() && file.isFile)) return BuildRequirements() + + var minGradle: SemVer? = null + var maxGradle: SemVer? = null + + file.readLines().forEach { line -> + when { + line.startsWith("MIN_GRADLE=") -> minGradle = + SemVer.ofVariableOrNull(line.substringAfter("MIN_GRADLE=")) + line.startsWith("MAX_GRADLE=") -> maxGradle = + SemVer.ofVariableOrNull(line.substringAfter("MAX_GRADLE=")) + + } + } + + return BuildRequirements(minGradle, maxGradle) + } + +internal data class BuildRequirements(val minGradle: SemVer? = null, val maxGradle: SemVer? = null) + +internal val File.buildSrc: File? + get() = listFiles()?.firstOrNull { it.isDirectory && it.name == "buildSrc" } + +internal val File.settings: File + get() = listFiles()?.firstOrNull { it.name == "settings.gradle" || it.name == "settings.gradle.kts" } + ?: throw Exception("No Gradle settings file in project ${this.uri}") +internal val File.build: File + get() = listFiles()?.firstOrNull { it.name == "build.gradle" || it.name == "build.gradle.kts" } + ?: throw Exception("No Gradle build file in project ${this.uri}") + + /** * Override Kover version and add local repository to find artifact for current build. @@ -84,13 +119,11 @@ internal fun File.patchSettingsFile( localRepositoryPath: String, overrideKotlinVersion: String? ) { - val settingsFile = (listFiles()?.firstOrNull { it.name == "settings.gradle" || it.name == "settings.gradle.kts" } - ?: throw Exception("No Gradle settings file in project ${this.uri}")) - val language = if (settingsFile.name.endsWith(".kts")) ScriptLanguage.KOTLIN else ScriptLanguage.GROOVY + val language = if (name.endsWith(".kts")) ScriptLanguage.KOTLIN else ScriptLanguage.GROOVY - val originLines = settingsFile.readLines() + val originLines = readLines() - settingsFile.bufferedWriter().use { writer -> + bufferedWriter().use { writer -> var firstStatement = true originLines.forEach { line -> @@ -132,4 +165,33 @@ internal fun File.patchSettingsFile( } } +/** + * Override Kover version + */ +internal fun File.patchKoverDependency(koverVersion: String) { + val originLines = readLines() + bufferedWriter().use { writer -> + originLines.forEach { line -> + val lineToWrite = if (line.contains("org.jetbrains.kotlinx:kover-gradle-plugin:TEST")) { + line.replace("org.jetbrains.kotlinx:kover-gradle-plugin:TEST", "org.jetbrains.kotlinx:kover-gradle-plugin:$koverVersion") + } else { + line + } + writer.appendLine(lineToWrite) + } + } +} + +/** + * Override Kover version + */ +internal fun File.addLocalRepository(path: String) { + this.appendText(""" + + repositories { + maven("$path") + } + """.trimIndent()) +} + diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/build.gradle.kts b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/build.gradle.kts new file mode 100644 index 00000000..34b05f93 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("jvm") version "1.4.20" +} + +apply(plugin = "org.jetbrains.kotlinx.kover") + +repositories { + mavenCentral() +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter:5.8.0") +} + +tasks.test { + useJUnitPlatform() +} + diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/buildSrc/build.gradle.kts b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/buildSrc/build.gradle.kts new file mode 100644 index 00000000..a4b408f1 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/buildSrc/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + kotlin("jvm") version "1.4.20" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.jetbrains.kotlinx:kover-gradle-plugin:TEST") +} diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/buildSrc/src/main/kotlin/Usage.kt b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/buildSrc/src/main/kotlin/Usage.kt new file mode 100644 index 00000000..7696fd7a --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/buildSrc/src/main/kotlin/Usage.kt @@ -0,0 +1,2 @@ +class Class { +} \ No newline at end of file diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/requires b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/requires new file mode 100644 index 00000000..d1ae18cd --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/requires @@ -0,0 +1 @@ +MAX_GRADLE=8.0 \ No newline at end of file diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/settings.gradle.kts b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/settings.gradle.kts new file mode 100644 index 00000000..06c80828 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} +rootProject.name = "buildsrc-usage" + diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/src/main/kotlin/ExampleClass.kt b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/src/main/kotlin/ExampleClass.kt new file mode 100644 index 00000000..d51001d4 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/src/main/kotlin/ExampleClass.kt @@ -0,0 +1,9 @@ +class ExampleClass { + fun example() { + println("example") + } + + fun unused() { + println("unused") + } +} \ No newline at end of file diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/src/test/kotlin/TestClass.kt b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/src/test/kotlin/TestClass.kt new file mode 100644 index 00000000..1b6bf5b6 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/buildsrc-usage/src/test/kotlin/TestClass.kt @@ -0,0 +1,8 @@ +import org.junit.jupiter.api.Test + +class TestClass { + @Test + fun testBranches() { + ExampleClass().example() + } +} \ No newline at end of file diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt index f271bfdf..f8d77eb6 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt @@ -39,7 +39,7 @@ import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.register import org.gradle.language.base.plugins.LifecycleBasePlugin -internal sealed class ReportsVariantApplier( +internal abstract class ReportsVariantApplier( private val project: Project, private val variantName: String, private val type: ReportsVariantType, @@ -58,14 +58,16 @@ internal sealed class ReportsVariantApplier( init { artifactGenTask = project.tasks.register(artifactGenerationTaskName(variantName)) + + val artifactProperty = project.layout.buildDirectory.file(artifactFilePath(variantName)) artifactGenTask.configure { - artifactFile.set(project.layout.buildDirectory.file(artifactFilePath(variantName))) + artifactFile.set(artifactProperty) } config = project.configurations.register(artifactConfigurationName(variantName)) { // disable generation of Kover artifacts on `assemble`, fix of https://github.com/Kotlin/kotlinx-kover/issues/353 isVisible = false - outgoing.artifact(artifactGenTask.map { task -> task.artifactFile }) { + outgoing.artifact(artifactProperty) { asProducer() attributes { // common Kover artifact attributes