Skip to content

Commit

Permalink
Test Gradle plugin on relevant PRs (#2509)
Browse files Browse the repository at this point in the history
* Update Gradle used in tooling subprojects

* Update Kotlin in Compose Gradle plugin

* Decrease verbosity of Gradle plugin tests

* Disable mac sign test

* Add workflow to test Gradle plugin

* Fix custom jdk tests on Linux

* Make Compose Gradle plugin build compatible with Configuration cache

* Print tests summary

* Remove unused code

* Refactor tests configuration

* Turn off parallel execution

* Try adding windows runner

* Turn off fail fast

* Fix Windows test issues

#2368

* Adjust default proguard rules

The following rule is needed to fix tests on Windows:
```
-dontwarn org.graalvm.compiler.core.aarch64.AArch64NodeMatchRules_MatchStatementSet*
```

Other rules are just to make builds less noisy.
Kotlin's `*.internal` packages often contain
bytecode, which triggers ProGuard's notes.
However, these notes are not actionable for
most users, so we can ignore notes by default.

#2393
  • Loading branch information
AlexeyTsvetkov authored Nov 30, 2022
1 parent 7e574a0 commit 382ad5b
Show file tree
Hide file tree
Showing 23 changed files with 448 additions and 128 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/gradle-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Test Gradle plugin
on:
pull_request:
paths:
- 'gradle-plugins/**'
- '.github/workflows/gradle-plugin.yml'
jobs:
test-gradle-plugin:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-12, windows-2022]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: '16'
- name: Test Gradle plugin
shell: bash
run: |
cd gradle-plugins
./gradlew assemble
./gradlew :compose:check --continue
- name: Print summary
shell: bash
if: always()
run: |
cd gradle-plugins/compose/build/test-summary
for SUMMARY_FILE in `find . -name "*.md"`; do
FILE_NAME=`basename $SUMMARY_FILE`
echo "## $FILE_NAME" >> $GITHUB_STEP_SUMMARY
cat $SUMMARY_FILE >> $GITHUB_STEP_SUMMARY
done
12 changes: 11 additions & 1 deletion gradle-plugins/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import com.gradle.publish.PluginBundleExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile

plugins {
val kotlinVersion = "1.5.30"
val kotlinVersion = "1.7.20"
kotlin("jvm") version kotlinVersion apply false
kotlin("plugin.serialization") version kotlinVersion apply false
id("com.gradle.plugin-publish") version "0.17.0" apply false
Expand All @@ -26,6 +27,15 @@ subprojects {
}
}

plugins.withId("org.jetbrains.kotlin.jvm") {
tasks.withType(KotlinJvmCompile::class).configureEach {
// must be set to a language version of the kotlin compiler & runtime,
// which is bundled to the oldest supported Gradle
kotlinOptions.languageVersion = "1.5"
kotlinOptions.apiVersion = "1.5"
}
}

plugins.withId("maven-publish") {
configureIfExists<PublishingExtension> {
repositories {
Expand Down
65 changes: 65 additions & 0 deletions gradle-plugins/buildSrc/src/main/kotlin/CheckJarPackagesTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/

import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFile
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import java.util.*
import java.util.zip.ZipFile
import javax.inject.Inject

/**
* Checks that every class in a [jarFile] matches one of [allowedPackagePrefixes]
*/
abstract class CheckJarPackagesTask @Inject constructor(
objects: ObjectFactory
) : DefaultTask() {
@get:InputFile
val jarFile: Property<RegularFile> = objects.fileProperty()

@get:Input
val allowedPackagePrefixes: SetProperty<String> = objects.setProperty(String::class.java)

@TaskAction
fun run() {
ZipFile(jarFile.get().asFile).use { zip ->
checkJarContainsExpectedPackages(zip)
}
}

private fun checkJarContainsExpectedPackages(jar: ZipFile) {
val unexpectedClasses = arrayListOf<String>()
val allowedPrefixes = allowedPackagePrefixes.get().map { it.replace(".", "/") }

for (entry in jar.entries()) {
if (entry.isDirectory || !entry.name.endsWith(".class")) continue

if (allowedPrefixes.none { prefix -> entry.name.startsWith(prefix) }) {
unexpectedClasses.add(entry.name)
}
}

if (unexpectedClasses.any()) {
error(buildString {
appendLine("All classes in ${jar.name} must match allowed prefixes:")
allowedPrefixes.forEach {
appendLine(" * $it")
}
appendLine("Non-valid classes:")
val unexpectedGroups = unexpectedClasses
.groupByTo(TreeMap()) { it.substringBeforeLast("/") }
for ((_, classes) in unexpectedGroups) {
appendLine(" * ${classes.first()}")
}
})
}
}
}

32 changes: 32 additions & 0 deletions gradle-plugins/buildSrc/src/main/kotlin/SerializeClasspathTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/

import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
import javax.inject.Inject

abstract class SerializeClasspathTask @Inject constructor(
objects: ObjectFactory
) : DefaultTask() {
@get:InputFiles
val classpathFileCollection: ConfigurableFileCollection = objects.fileCollection()

@get:OutputFile
val outputFile: RegularFileProperty = objects.fileProperty()

@TaskAction
fun run() {
val classpath = classpathFileCollection.files.joinToString(File.pathSeparator) { it.absolutePath }
val outputFile = outputFile.get().asFile
outputFile.parentFile.mkdirs()
outputFile.writeText(classpath)
}
}
38 changes: 36 additions & 2 deletions gradle-plugins/buildSrc/src/main/kotlin/gradleUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@

import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import java.io.File
Expand All @@ -35,19 +42,46 @@ fun Test.configureJavaForComposeTest() {
}
}

fun Project.configureJUnit() {
fun Project.configureAllTests(fn: Test.() -> Unit = {}) {
fun DependencyHandler.testImplementation(notation: Any) =
add(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, notation)

dependencies {
testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.platform:junit-platform-launcher")
}

tasks.withType<Test>().configureEach {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
fn()
}
}
}

fun Test.systemProperties(map: Map<String, Any>) {
for ((k, v) in map) {
systemProperty(k, v)
}
}

fun TaskProvider<*>.dependsOn(vararg dependencies: Any) {
configure {
dependsOn(dependencies)
}
}

inline fun <reified T : Task> TaskContainer.registerVerificationTask(
name: String,
crossinline fn: T.() -> Unit
): TaskProvider<T> =
register(name, T::class) {
fn()
}.apply {
named("check").dependsOn(this)
}

val Provider<out Jar>.archiveFile: Provider<RegularFile>
get() = flatMap { it.archiveFile }
87 changes: 14 additions & 73 deletions gradle-plugins/compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem
import java.util.zip.ZipFile

plugins {
kotlin("jvm")
Expand Down Expand Up @@ -61,8 +59,6 @@ dependencies {
compileOnly(kotlin("native-utils"))

testImplementation(gradleTestKit())
testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation(kotlin("gradle-plugin-api"))

// include relocated download task to avoid potential runtime conflicts
Expand All @@ -89,62 +85,17 @@ val jar = tasks.named<Jar>("jar") {
this.duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

// __SUPPORTED_GRADLE_VERSIONS__
//testGradleVersion("6.7.1") // min supported by kotlin 1.7.0 gradle plugin https://kotlinlang.org/docs/gradle.html
// despite that, some tests didn't pass
testGradleVersion("7.1.1")
testGradleVersion("7.3.3")

val javaHomeForTests: String? = when {
// __COMPOSE_NATIVE_DISTRIBUTIONS_MIN_JAVA_VERSION__
JavaVersion.current() >= JavaVersion.VERSION_15 -> System.getProperty("java.home")
else -> System.getenv("JDK_15")
?: System.getenv("JDK_FOR_GRADLE_TESTS")
}
val isWindows = getCurrentOperatingSystem().isWindows
val supportedGradleVersions = project.property("compose.tests.gradle.versions")
.toString().split(",")
.map { it.trim() }

val gradleTestsPattern = "org.jetbrains.compose.test.tests.integration.*"

// check we don't accidentally including unexpected classes (e.g. from embedded dependencies)
val checkJar by tasks.registering {
tasks.registerVerificationTask<CheckJarPackagesTask>("checkJar") {
dependsOn(jar)

doLast {
val file = jar.get().archiveFile.get().asFile
ZipFile(file).use { zip ->
checkJarContainsExpectedPackages(zip)
}
}
}

// we want to avoid accidentally including unexpected jars/packages, e.g kotlin-stdlib etc
fun checkJarContainsExpectedPackages(jar: ZipFile) {
val expectedPackages = arrayOf(
"org/jetbrains/compose",
"kotlinx/serialization"
)
val unexpectedClasses = arrayListOf<String>()

for (entry in jar.entries()) {
if (entry.isDirectory || !entry.name.endsWith(".class")) continue

if (expectedPackages.none { prefix -> entry.name.startsWith(prefix) }) {
unexpectedClasses.add(entry.name)
}
}

if (unexpectedClasses.any()) {
error(buildString {
appendLine("Some classes from ${jar.name} are not from 'org.jetbrains.compose' package:")
unexpectedClasses.forEach {
appendLine(" * $it")
}
})
}
}

tasks.check {
dependsOn(checkJar)
jarFile.set(jar.archiveFile)
allowedPackagePrefixes.addAll("org.jetbrains.compose", "kotlinx.serialization")
}

tasks.test {
Expand All @@ -154,34 +105,24 @@ tasks.test {
excludeTestsMatching(gradleTestsPattern)
}
}
fun testGradleVersion(gradleVersion: String) {
val taskProvider = tasks.register("testGradle-$gradleVersion", Test::class) {
tasks.test.get().let { defaultTest ->
classpath = defaultTest.classpath
}

for (gradleVersion in supportedGradleVersions) {
tasks.registerVerificationTask<Test>("testGradle-$gradleVersion") {
classpath = tasks.test.get().classpath
systemProperty("compose.tests.gradle.version", gradleVersion)
filter {
includeTestsMatching(gradleTestsPattern)
}
}
tasks.named("check") {
dependsOn(taskProvider)
}
}

configureJUnit()

tasks.withType<Test>().configureEach {
configureAllTests {
configureJavaForComposeTest()

dependsOn(":publishToMavenLocal")

systemProperty("compose.tests.compose.gradle.plugin.version", BuildProperties.deployVersion(project))
for ((k, v) in project.properties) {
if (k.startsWith("compose.")) {
systemProperty(k, v.toString())
}
}
val summaryDir = project.buildDir.resolve("test-summary")
systemProperty("compose.tests.summary.file", summaryDir.resolve("$name.md").absolutePath)
systemProperties(project.properties.filter { it.key.startsWith("compose.") })
}

task("printAllAndroidxReplacements") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {
KotlinPlatformType.js -> isApplicableJsTarget(kotlinCompilation.target)
KotlinPlatformType.androidJvm -> true
KotlinPlatformType.native -> true
KotlinPlatformType.wasm -> false
}

private fun isApplicableJsTarget(kotlinTarget: KotlinTarget): Boolean {
Expand Down
Loading

0 comments on commit 382ad5b

Please sign in to comment.