Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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')
Expand Down
96 changes: 96 additions & 0 deletions src/test/groovy/org/gradle/android/JdkImageWorkaroundTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
"""
}
}
}
Loading