diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79a8cc5..bee6502 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ nexus = "1.3.0" ktlint = "0.50.0" spotless = "6.21.0" protokt = "1.0.0-alpha.10" +protobuf = "3.24.0" # test junit = "5.10.0" @@ -23,6 +24,7 @@ kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version. spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } gradle-publish = { module = "com.gradle.publish:plugin-publish-plugin", version = "1.2.0" } protokt-extensions = { module = "com.toasttab.protokt:protokt-extensions", version.ref = "protokt" } +protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" } # test junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 2a160f7..06ef1fc 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -8,6 +8,7 @@ plugins { dependencies { implementation(projects.core) implementation(projects.animalSnifferFormat) + implementation(libs.protobuf.java) testImplementation(libs.testkit.junit5) } diff --git a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterExtension.kt b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterExtension.kt index ec80299..03f909f 100644 --- a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterExtension.kt +++ b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterExtension.kt @@ -19,7 +19,7 @@ import org.gradle.api.Action abstract class ExpediterExtension { var application: ApplicationClassSelector = ApplicationClassSelector(configuration = "runtimeClasspath", sourceSet = "main") - var platform: PlatformClassSelector = PlatformClassSelector(platformClassloader = true) + var platform: PlatformClassSelector = PlatformClassSelector() val ignoreSpec = IgnoreSpec() @@ -34,7 +34,6 @@ abstract class ExpediterExtension { } fun platform(configure: Action) { - platform = PlatformClassSelector(false) configure.execute(platform) } diff --git a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterPlugin.kt b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterPlugin.kt index cb094e8..96a4d58 100644 --- a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterPlugin.kt +++ b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterPlugin.kt @@ -44,23 +44,31 @@ class ExpediterPlugin : Plugin { jvmVersion = extension.platform.jvmVersion - val animalSnifferConfigurations = extension.platform.animalSnifferConfigurations.toMutableList() + val expediterConfigurations = extension.platform.expediterConfigurations.toMutableList() if (extension.platform.androidSdk != null) { - val config = project.configurations.create("_expediter_animal_sniffer_") + val config = project.configurations.create("_expediter_type_descriptors_") project.dependencies.add( config.name, - "com.toasttab.android:gummy-bears-api-${extension.platform.androidSdk}:0.5.1@signature" + "com.toasttab.android:gummy-bears-api-${extension.platform.androidSdk}:0.6.0@expediter" ) - animalSnifferConfigurations.add(config.name) + expediterConfigurations.add(config.name) + } + + for (conf in expediterConfigurations) { + typeDescriptors.from(project.configurations.getByName(conf)) + } + + for (conf in extension.platform.animalSnifferConfigurations) { + animalSnifferSignatures.from(project.configurations.getByName(conf)) } if (extension.platform.jvmVersion != null && extension.platform.androidSdk != null) { logger.warn("Both jvmVersion and androidSdk are set.") } - for (conf in animalSnifferConfigurations) { - animalSnifferSignatures.from(project.configurations.getByName(conf)) + for (conf in extension.platform.configurations) { + platformArtifactCollection(selector.artifacts(conf)) } ignore = extension.ignoreSpec.buildIgnore() diff --git a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt index 8764b0f..d408434 100644 --- a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt +++ b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/ExpediterTask.kt @@ -16,7 +16,9 @@ package com.toasttab.expediter.gradle import com.toasttab.expediter.ClasspathApplicationTypesProvider +import com.toasttab.expediter.ClasspathScanner import com.toasttab.expediter.Expediter +import com.toasttab.expediter.TypeParsers import com.toasttab.expediter.ignore.Ignore import com.toasttab.expediter.issue.IssueReport import com.toasttab.expediter.sniffer.AnimalSnifferParser @@ -39,22 +41,27 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import protokt.v1.toasttab.expediter.v1.TypeDescriptors import java.io.File +import java.util.zip.GZIPInputStream @CacheableTask abstract class ExpediterTask : DefaultTask() { - private val configurationArtifacts = mutableListOf() + private val applicationConfigurationArtifacts = mutableListOf() + private val platformConfigurationArtifacts = mutableListOf() @get:InputFiles @get:PathSensitive(PathSensitivity.ABSOLUTE) - val artifacts: FileCollection get() { - return if (configurationArtifacts.isEmpty()) { - project.objects.fileCollection() - } else { - configurationArtifacts.map { - it.artifactFiles - }.reduce(FileCollection::plus) - } + val applicationArtifacts get() = applicationConfigurationArtifacts.asFileCollection() + + @get:InputFiles + @get:PathSensitive(PathSensitivity.ABSOLUTE) + val platformArtifacts get() = platformConfigurationArtifacts.asFileCollection() + + private fun Collection.asFileCollection() = if (isEmpty()) { + project.objects.fileCollection() + } else { + map { it.artifactFiles }.reduce(FileCollection::plus) } @get:InputFiles @@ -62,7 +69,11 @@ abstract class ExpediterTask : DefaultTask() { abstract val files: ConfigurableFileCollection fun artifactCollection(artifactCollection: ArtifactCollection) { - configurationArtifacts.add(artifactCollection) + applicationConfigurationArtifacts.add(artifactCollection) + } + + fun platformArtifactCollection(artifactCollection: ArtifactCollection) { + platformConfigurationArtifacts.add(artifactCollection) } @OutputFile @@ -79,6 +90,10 @@ abstract class ExpediterTask : DefaultTask() { @get:PathSensitive(PathSensitivity.RELATIVE) abstract val animalSnifferSignatures: ConfigurableFileCollection + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val typeDescriptors: ConfigurableFileCollection + @InputFile @PathSensitive(PathSensitivity.RELATIVE) @Optional @@ -95,13 +110,27 @@ abstract class ExpediterTask : DefaultTask() { providers.add(JvmTypeProvider.forTarget(it)) } + if (!platformArtifacts.isEmpty) { + providers.add( + InMemoryPlatformTypeProvider( + ClasspathScanner(platformArtifacts).scan { i, _ -> TypeParsers.typeDescriptor(i) } + ) + ) + } + for (signaturesFile in animalSnifferSignatures) { signaturesFile.inputStream().buffered().use { providers.add(InMemoryPlatformTypeProvider(AnimalSnifferParser.parse(it))) } } - if (jvmVersion == null && animalSnifferSignatures.isEmpty) { + for (descriptorFile in typeDescriptors) { + GZIPInputStream(descriptorFile.inputStream().buffered()).use { + providers.add(InMemoryPlatformTypeProvider(TypeDescriptors.deserialize(it).types)) + } + } + + if (jvmVersion == null && animalSnifferSignatures.isEmpty && typeDescriptors.isEmpty) { logger.warn("No platform APIs specified, falling back to the platform classloader of the current JVM.") providers.add(PlatformClassloaderTypeProvider) @@ -115,7 +144,7 @@ abstract class ExpediterTask : DefaultTask() { val issues = Expediter( ignore, - ClasspathApplicationTypesProvider(artifacts + files), + ClasspathApplicationTypesProvider(applicationArtifacts + files), PlatformTypeProviderChain( providers ) diff --git a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/PlatformClassSelector.kt b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/PlatformClassSelector.kt index 5319e4c..ae0cf33 100644 --- a/plugin/src/main/kotlin/com/toasttab/expediter/gradle/PlatformClassSelector.kt +++ b/plugin/src/main/kotlin/com/toasttab/expediter/gradle/PlatformClassSelector.kt @@ -16,12 +16,21 @@ package com.toasttab.expediter.gradle class PlatformClassSelector( - var platformClassloader: Boolean, val animalSnifferConfigurations: MutableList = mutableListOf(), + val expediterConfigurations: MutableList = mutableListOf(), + val configurations: MutableList = mutableListOf(), var androidSdk: Int? = null, var jvmVersion: Int? = null ) { fun animalSnifferConfiguration(configuration: String) { animalSnifferConfigurations.add(configuration) } + + fun expediterConfiguration(configuration: String) { + expediterConfigurations.add(configuration) + } + + fun configuration(configuration: String) { + configurations.add(configuration) + } } diff --git a/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt b/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt index 992b5b2..3e14af5 100644 --- a/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt +++ b/plugin/src/test/kotlin/com/toasttab/expediter/gradle/ExpediterPluginIntegrationTest.kt @@ -38,6 +38,35 @@ class ExpediterPluginIntegrationTest { val report = IssueReport.fromJson(project.dir.resolve("build/expediter.json").readText()) + expectThat(report.issues).contains( + Issue.MissingMember( + "test/Caller", + MemberAccess.MethodAccess( + "java/util/concurrent/ConcurrentHashMap", + null, + MemberSymbolicReference( + "computeIfAbsent", + "(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;" + ), + MethodAccessType.VIRTUAL + ) + ), + + Issue.MissingType( + "com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder", + "java/util/stream/Collectors" + ) + ) + } + + @Test + fun `android compat source only`(project: TestProject) { + project.createRunner() + .withArguments("check") + .buildAndFail() + + val report = IssueReport.fromJson(project.dir.resolve("build/expediter.json").readText()) + expectThat(report.issues).containsExactlyInAnyOrder( Issue.MissingMember( "test/Caller", diff --git a/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/build.gradle.kts b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/build.gradle.kts new file mode 100644 index 0000000..24524a0 --- /dev/null +++ b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + java + id("com.toasttab.expediter") + id("com.toasttab.testkit.coverage") version "0.0.2" +} + +repositories { + mavenCentral() +} + +expediter { + failOnIssues = true + + application { + sourceSet("main") + } + + platform { + androidSdk = 19 + + configuration("runtimeClasspath") + } +} + +dependencies { + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") +} diff --git a/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/settings.gradle.kts b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/settings.gradle.kts new file mode 100644 index 0000000..dd508ac --- /dev/null +++ b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "test" \ No newline at end of file diff --git a/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/src/main/java/test/Caller.java b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/src/main/java/test/Caller.java new file mode 100644 index 0000000..fa2edff --- /dev/null +++ b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat source only/src/main/java/test/Caller.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Toast Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test; + +import java.util.concurrent.ConcurrentHashMap; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class Caller { + void f() { + ConcurrentHashMap map = new ConcurrentHashMap<>(); + + map.put("a", "b"); + + map.computeIfAbsent("a", k -> "b"); + + map.hashCode(); + } + + void g() { + ObjectMapper mapper = new ObjectMapper(); + } +} \ No newline at end of file diff --git a/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat/build.gradle.kts b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat/build.gradle.kts index dfd898c..383748e 100644 --- a/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat/build.gradle.kts +++ b/plugin/src/test/projects/ExpediterPluginIntegrationTest/android compat/build.gradle.kts @@ -11,11 +11,11 @@ repositories { expediter { failOnIssues = true - application { - sourceSet("main") - } - platform { androidSdk = 19 } } + +dependencies { + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") +}