diff --git a/README.md b/README.md index a877598f..4265bdbb 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,16 @@ To work around this issue, please apply the [Room Gradle Plugin](https://develop ## Implementation Notes +### JdkImageWorkaround +This workaround addresses issues with the JdkImageInput compiler argument, introduced in Android Gradle Plugin 7.1.0 and later. The JdkImageInput argument can introduce non-determinism in the build process, leading to cache misses. + +Additionally, the workaround applies runtime classpath normalization by ignoring `metaInf` attributes and ignoring `**/java/lang/invoke/**`. The latter exclusion is primarily based on our observations when comparing builds across different architectures, and it was also discussed in the related issue https://github.com/gradle/android-cache-fix-gradle-plugin/issues/341. + +You can opt out of this normalization by adding the following property to your gradle.properties file: +``` +systemProp.org.gradle.android.cache-fix.JdkImageWorkaround.normalization.enabled=false +``` + ### MergeNativeLibs, StripDebugSymbols, MergeJavaResources, MergeSourceSetFolders, BundleLibraryClassesJar, DataBindingMergeDependencyArtifacts, LibraryJniLibs and ZipMerging Workarounds It has been observed that caching the `MergeNativeLibsTask`, `StripDebugSymbols`, `MergeSourceSetFolders`, `BundleLibraryClassesJar`, `DataBindingMergeDependencyArtifacts`, `LibraryJniLibs` and `ZipMergingTask` tasks rarely provide any significant positive avoidance savings. In fact, they frequently provide negative savings, especially when fetched from a remote cache node. As such, these workarounds disable caching for these tasks. diff --git a/src/main/groovy/org/gradle/android/workarounds/JdkImageWorkaround.groovy b/src/main/groovy/org/gradle/android/workarounds/JdkImageWorkaround.groovy index 6034354c..fd540971 100644 --- a/src/main/groovy/org/gradle/android/workarounds/JdkImageWorkaround.groovy +++ b/src/main/groovy/org/gradle/android/workarounds/JdkImageWorkaround.groovy @@ -48,6 +48,7 @@ import java.util.stream.Stream @AndroidIssue(introducedIn = "7.1.0", link = "https://issuetracker.google.com/issues/267213045") class JdkImageWorkaround implements Workaround { static final String WORKAROUND_ENABLED_PROPERTY = "org.gradle.android.cache-fix.JdkImageWorkaround.enabled" + static final String WORKAROUND_INVOKE_NORMALIZATION_PROPERTY = "org.gradle.android.cache-fix.JdkImageWorkaround.normalization.enabled" static final String JDK_IMAGE = "_internal_android_jdk_image" static final String JDK_IMAGE_EXTRACTED = "_internal_android_jdk_image_extracted" @@ -115,6 +116,9 @@ class JdkImageWorkaround implements Workaround { static def applyRuntimeClasspathNormalization(Project project) { project.normalization { handler -> handler.runtimeClasspath { + if (SystemPropertiesCompat.getBoolean(WORKAROUND_INVOKE_NORMALIZATION_PROPERTY, project, true)) { + it.ignore '**/java/lang/invoke/**' + } it.metaInf { metaInfNormalization -> metaInfNormalization.ignoreAttribute('Implementation-Version') metaInfNormalization.ignoreAttribute('Implementation-Vendor') diff --git a/src/test/groovy/org/gradle/android/JdkImageWorkaroundTest.groovy b/src/test/groovy/org/gradle/android/JdkImageWorkaroundTest.groovy index f88ab426..89d8296e 100644 --- a/src/test/groovy/org/gradle/android/JdkImageWorkaroundTest.groovy +++ b/src/test/groovy/org/gradle/android/JdkImageWorkaroundTest.groovy @@ -308,4 +308,100 @@ class JdkImageWorkaroundTest extends AbstractTest { where: androidVersion << TestVersions.latestAndroidVersions } + + def "**/java/lang/invoke/** is normalized by default"() { + def androidVersion = TestVersions.latestAndroidVersionForCurrentJDK() + def gradleVersion = TestVersions.latestSupportedGradleVersionFor(androidVersion) + Assume.assumeTrue(androidVersion >= VersionNumber.parse("7.1.0-alpha01")) + SimpleAndroidApp.builder(temporaryFolder.root, cacheDir) + .withAndroidVersion(androidVersion) + .withKotlinDisabled() + .withDatabindingDisabled() + .build() + .writeProject() + createLibJavaClass("firstBuild") + + when: + BuildResult buildResult = withGradleVersion(gradleVersion.version) + .withProjectDir(temporaryFolder.root) + .withArguments( + "clean", "test", "assemble", + "--build-cache" + ).build() + then: + buildResult.task(':app:compileDebugJavaWithJavac').outcome == TaskOutcome.SUCCESS + buildResult.task(':library:compileDebugJavaWithJavac').outcome == TaskOutcome.SUCCESS + + buildResult.task(':app:compileDebugUnitTestJavaWithJavac').outcome == TaskOutcome.SUCCESS + buildResult.task(':library:compileDebugUnitTestJavaWithJavac').outcome == TaskOutcome.SUCCESS + + when: + createLibJavaClass("secondBuild") + buildResult = withGradleVersion(gradleVersion.version) + .withProjectDir(temporaryFolder.root) + .withArguments( + "clean", "test", "assemble", + "--build-cache" + ).build() + + then: + buildResult.task(':app:testDebugUnitTest').outcome == TaskOutcome.FROM_CACHE + } + + def "**/java/lang/invoke/** is not normalized when normalization property is false"() { + def androidVersion = TestVersions.latestAndroidVersionForCurrentJDK() + def gradleVersion = TestVersions.latestSupportedGradleVersionFor(androidVersion) + Assume.assumeTrue(androidVersion >= VersionNumber.parse("7.1.0-alpha01")) + SimpleAndroidApp.builder(temporaryFolder.root, cacheDir) + .withAndroidVersion(androidVersion) + .withKotlinDisabled() + .withDatabindingDisabled() + .build() + .writeProject() + createLibJavaClass("firstBuild") + + when: + BuildResult buildResult = withGradleVersion(gradleVersion.version) + .withProjectDir(temporaryFolder.root) + .withArguments( + "clean", "test", "assemble", + "--build-cache", + "-D${JdkImageWorkaround.WORKAROUND_INVOKE_NORMALIZATION_PROPERTY}=false" + ).build() + then: + buildResult.task(':app:compileDebugJavaWithJavac').outcome == TaskOutcome.SUCCESS + buildResult.task(':library:compileDebugJavaWithJavac').outcome == TaskOutcome.SUCCESS + + buildResult.task(':app:compileDebugUnitTestJavaWithJavac').outcome == TaskOutcome.SUCCESS + buildResult.task(':library:compileDebugUnitTestJavaWithJavac').outcome == TaskOutcome.SUCCESS + + when: + createLibJavaClass("secondBuild") + + buildResult = withGradleVersion(gradleVersion.version) + .withProjectDir(temporaryFolder.root) + .withArguments( + "clean", "test", "assemble", + "--build-cache", + "-D${JdkImageWorkaround.WORKAROUND_INVOKE_NORMALIZATION_PROPERTY}=false" + ).build() + + then: + buildResult.task(':app:testDebugUnitTest').outcome == TaskOutcome.SUCCESS + } + + def createLibJavaClass(String label) { + file("library/src/main/java/com/foo/java/lang/invoke/Bar.java") + .createParentDirectories() + .withWriter { w -> + w << """ + package com.foo.java.lang.invoke; + public class Bar { + public static String bar() { + return "$label"; + } + } + """ + } + } }