From 4a51ffe090ff5f07d64a182d209e7237769a289c Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Tue, 27 Sep 2022 22:22:10 +0200 Subject: [PATCH] Support reading classes from resulting jar and allow configuring tasks without registering extension --- .../BinaryCompatibilityValidatorPlugin.kt | 21 ++++----- src/main/kotlin/KotlinApiBuildTask.kt | 45 ++++++++++++++----- src/main/kotlin/KotlinApiCompareTask.kt | 10 +++++ 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 7b48672c..f9a98a47 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -179,7 +179,7 @@ private fun Project.configureKotlinCompilation( val apiDirProvider = targetConfig.apiDir val apiBuildDir = apiDirProvider.map { buildDir.resolve(it) } - val apiBuild = task(targetConfig.apiTaskName("Build"), extension) { + val apiBuild = task(targetConfig.apiTaskName("Build")) { // Do not enable task for empty umbrella modules isEnabled = apiCheckEnabled(projectName, extension) && compilation.allKotlinSourceSets.any { it.kotlin.srcDirs.any { it.exists() } } @@ -199,6 +199,9 @@ private fun Project.configureKotlinCompilation( files(provider { if (isEnabled) compilation.compileDependencyFiles else emptyList() }) } outputApiDir = apiBuildDir.get() + ignoredPackages = extension.ignoredPackages + ignoredClasses = extension.ignoredClasses + nonPublicMarkers = extension.nonPublicMarkers } configureCheckTasks(apiBuildDir, apiBuild, extension, targetConfig, commonApiDump, commonApiCheck) } @@ -216,7 +219,7 @@ private fun Project.configureApiTasks( ) { val projectName = project.name val apiBuildDir = targetConfig.apiDir.map { buildDir.resolve(it) } - val apiBuild = task(targetConfig.apiTaskName("Build"), extension) { + val apiBuild = task(targetConfig.apiTaskName("Build")) { isEnabled = apiCheckEnabled(projectName, extension) // 'group' is not specified deliberately so it will be hidden from ./gradlew tasks description = @@ -224,6 +227,9 @@ private fun Project.configureApiTasks( inputClassesDirs = files(provider { if (isEnabled) sourceSet.output.classesDirs else emptyList() }) inputDependencies = files(provider { if (isEnabled) sourceSet.output.classesDirs else emptyList() }) outputApiDir = apiBuildDir.get() + ignoredPackages = extension.ignoredPackages + ignoredClasses = extension.ignoredClasses + nonPublicMarkers = extension.nonPublicMarkers } configureCheckTasks(apiBuildDir, apiBuild, extension, targetConfig) @@ -247,16 +253,7 @@ private fun Project.configureCheckTasks( isEnabled = apiCheckEnabled(projectName, extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "verification" description = "Checks signatures of public API against the golden value in API folder for $projectName" - run { - val d = apiCheckDir.get() - projectApiDir = if (d.exists()) { - d - } else { - nonExistingProjectApiDir = d.toString() - null - } - this.apiBuildDir = apiBuildDir.get() - } + compareApiDumps(apiReferenceDir = apiCheckDir.get(), apiBuildDir = apiBuildDir.get()) dependsOn(apiBuild) } diff --git a/src/main/kotlin/KotlinApiBuildTask.kt b/src/main/kotlin/KotlinApiBuildTask.kt index af13e387..4f79b694 100644 --- a/src/main/kotlin/KotlinApiBuildTask.kt +++ b/src/main/kotlin/KotlinApiBuildTask.kt @@ -10,15 +10,21 @@ import org.gradle.api.* import org.gradle.api.file.* import org.gradle.api.tasks.* import java.io.* +import java.util.jar.JarFile import javax.inject.Inject open class KotlinApiBuildTask @Inject constructor( - private val extension: ApiValidationExtension ) : DefaultTask() { @InputFiles + @Optional @PathSensitive(PathSensitivity.RELATIVE) - lateinit var inputClassesDirs: FileCollection + var inputClassesDirs: FileCollection? = null + + @InputFile + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + val inputJar: RegularFileProperty = this.project.objects.fileProperty() @InputFiles @PathSensitive(PathSensitivity.RELATIVE) @@ -28,13 +34,13 @@ open class KotlinApiBuildTask @Inject constructor( lateinit var outputApiDir: File @get:Input - val ignoredPackages : Set get() = extension.ignoredPackages + var ignoredPackages : Set = emptySet() @get:Input - val nonPublicMarkers : Set get() = extension.nonPublicMarkers + var nonPublicMarkers : Set = emptySet() @get:Input - val ignoredClasses : Set get() = extension.ignoredClasses + var ignoredClasses : Set = emptySet() @get:Internal internal val projectName = project.name @@ -44,17 +50,32 @@ open class KotlinApiBuildTask @Inject constructor( cleanup(outputApiDir) outputApiDir.mkdirs() - val signatures = inputClassesDirs.asFileTree.asSequence() - .filter { - !it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/") - } - .map { it.inputStream() } - .loadApiFromJvmClasses() + val inputClassesDirs = inputClassesDirs + if (listOfNotNull(inputClassesDirs, inputJar.orNull).size != 1) { + throw GradleException("KotlinApiBuildTask should have either inputClassesDirs, or inputJar properties set") + } + val signatures = when { + inputClassesDirs != null -> + inputClassesDirs.asFileTree.asSequence() + .filter { + !it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/") + } + .map { it.inputStream() } + .loadApiFromJvmClasses() + inputJar.isPresent -> + JarFile(inputJar.get().asFile) + .loadApiFromJvmClasses() + else -> + error("Unreachable") + } + + + val filteredSignatures = signatures .filterOutNonPublic(ignoredPackages, ignoredClasses) .filterOutAnnotated(nonPublicMarkers.map { it.replace(".", "/") }.toSet()) outputApiDir.resolve("$projectName.api").bufferedWriter().use { writer -> - signatures + filteredSignatures .sortedBy { it.name } .forEach { api -> writer.append(api.signature).appendLine(" {") diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index 25b35118..6ca711e6 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -32,6 +32,16 @@ open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectF @Optional var nonExistingProjectApiDir: String? = null + fun compareApiDumps(apiReferenceDir: File, apiBuildDir: File) { + if (apiReferenceDir.exists()) { + projectApiDir = apiReferenceDir + } else { + projectApiDir = null + nonExistingProjectApiDir = apiReferenceDir.toString() + } + this.apiBuildDir = apiBuildDir + } + @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) lateinit var apiBuildDir: File