From b3a048fff9d8d426186a1f8c3065ae56f2937b6d Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 18 May 2022 15:13:29 +0600 Subject: [PATCH] Fix case-sensitivity of FS lookup (#84) Fixes #76 --- .../BinaryCompatibilityValidatorPlugin.kt | 4 +- ...CompareTask.kt => KotlinApiCompareTask.kt} | 39 ++++++++++++------- 2 files changed, 27 insertions(+), 16 deletions(-) rename src/main/kotlin/{ApiCompareCompareTask.kt => KotlinApiCompareTask.kt} (69%) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 05773401..96c428f5 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -64,7 +64,7 @@ class BinaryCompatibilityValidatorPlugin : Plugin { it.description = "Task that collects all target specific dump tasks" } - val commonApiCheck: TaskProvider? = project.tasks.register("apiCheck") { + val commonApiCheck: TaskProvider = project.tasks.register("apiCheck") { it.group = "verification" it.description = "Shortcut task that depends on all specific check tasks" }.apply { project.tasks.named("check") { it.dependsOn(this) } } @@ -243,7 +243,7 @@ private fun Project.configureCheckTasks( logger.debug("Configuring api for ${targetConfig.targetName ?: "jvm"} to $r") } } - val apiCheck = task(targetConfig.apiTaskName("Check")) { + val apiCheck = task(targetConfig.apiTaskName("Check")) { isEnabled = apiCheckEnabled(extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "verification" description = "Checks signatures of public API against the golden value in API folder for $projectName" diff --git a/src/main/kotlin/ApiCompareCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt similarity index 69% rename from src/main/kotlin/ApiCompareCompareTask.kt rename to src/main/kotlin/KotlinApiCompareTask.kt index b782b54c..25b35118 100644 --- a/src/main/kotlin/ApiCompareCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -11,10 +11,10 @@ import org.gradle.api.file.* import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.* import java.io.* -import java.util.TreeSet +import java.util.TreeMap import javax.inject.Inject -open class ApiCompareCompareTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { +open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { /* * Nullability and optionality is a workaround for @@ -38,6 +38,7 @@ open class ApiCompareCompareTask @Inject constructor(private val objects: Object @OutputFile @Optional + @Suppress("unused") val dummyOutputFile: File? = null private val projectName = project.name @@ -47,38 +48,48 @@ open class ApiCompareCompareTask @Inject constructor(private val objects: Object @TaskAction fun verify() { val projectApiDir = projectApiDir - if (projectApiDir == null) { - error("Expected folder with API declarations '$nonExistingProjectApiDir' does not exist.\n" + + ?: error("Expected folder with API declarations '$nonExistingProjectApiDir' does not exist.\n" + "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") - } val subject = projectName - val apiBuildDirFiles = mutableSetOf() - // We use case-insensitive comparison to workaround issues with case-insensitive OSes - // and Gradle behaving slightly different on different platforms - val expectedApiFiles = TreeSet { rp, rp2 -> + + /* + * We use case-insensitive comparison to workaround issues with case-insensitive OSes + * and Gradle behaving slightly different on different platforms. + * We neither know original sensitivity of existing .api files, not + * build ones, because projectName that is part of the path can have any sensitvity. + * To workaround that, we replace paths we are looking for the same paths that + * actually exist on FS. + */ + fun caseInsensitiveMap() = TreeMap { rp, rp2 -> rp.toString().compareTo(rp2.toString(), true) } + + val apiBuildDirFiles = caseInsensitiveMap() + val expectedApiFiles = caseInsensitiveMap() + objects.fileTree().from(apiBuildDir).visit { file -> - apiBuildDirFiles.add(file.relativePath) + apiBuildDirFiles[file.relativePath] = file.relativePath } objects.fileTree().from(projectApiDir).visit { file -> - expectedApiFiles.add(file.relativePath) + expectedApiFiles[file.relativePath] = file.relativePath } if (apiBuildDirFiles.size != 1) { error("Expected a single file $subject.api, but found: $expectedApiFiles") } - val expectedApiDeclaration = apiBuildDirFiles.single() + var expectedApiDeclaration = apiBuildDirFiles.keys.single() if (expectedApiDeclaration !in expectedApiFiles) { error("File ${expectedApiDeclaration.lastName} is missing from ${projectApiDir.relativePath()}, please run " + ":$subject:apiDump task to generate one") } - + // Normalize case-sensitivity + expectedApiDeclaration = expectedApiFiles.getValue(expectedApiDeclaration) + val actualApiDeclaration = apiBuildDirFiles.getValue(expectedApiDeclaration) val diffSet = mutableSetOf() val expectedFile = expectedApiDeclaration.getFile(projectApiDir) - val actualFile = expectedApiDeclaration.getFile(apiBuildDir) + val actualFile = actualApiDeclaration.getFile(apiBuildDir) val diff = compareFiles(expectedFile, actualFile) if (diff != null) diffSet.add(diff) if (diffSet.isNotEmpty()) {