KeepSafe/dexcount-gradle-plugin

Support Gradle configuration cache

CristianGM opened this issue ยท 9 comments

The configuration cache is a feature that significantly improves build performance by caching the result of the configuration phase and reusing this for subsequent builds.

Implementing support for the configuration cache in this plugin would improve users experience.

See https://docs.gradle.org/nightly/userguide/configuration_cache.html

The configuration cache will be introduced in Gradle 6.6.
But this can already be tested using 6.6-milestone-2.

Running the last Gradle Nightly + Kotlin 14-M2 + AGP 4.1.0-beta02 everything is working fine until we enable this plugin.

* What went wrong:
Configuration cache state could not be cached: field 'inputFileProperty' from type 'com.getkeepsafe.dexcount.DexCountTask': error writing value of type 'org.gradle.api.internal.file.DefaultFilePropertyFactory$DefaultRegularFileVar'
> Configuration cache state could not be cached: field 'provider' from type 'org.gradle.api.internal.provider.MappingProvider': error writing value of type 'org.gradle.api.internal.provider.TransformBackedProvider'
   > Configuration cache state could not be cached: field 'transformer' from type 'org.gradle.api.internal.provider.TransformBackedProvider': error writing value of type 'com.getkeepsafe.dexcount.ThreeThreeApplicator$applyToApkVariant$1$1$fileProvider$1'
      > Configuration cache state could not be cached: field 'this$0' from type 'com.getkeepsafe.dexcount.ThreeThreeApplicator$applyToApkVariant$1$1$fileProvider$1': error writing value of type 'com.getkeepsafe.dexcount.ThreeThreeApplicator$applyToApkVariant$1$1'
         > Configuration cache state could not be cached: field '$output' from type 'com.getkeepsafe.dexcount.ThreeThreeApplicator$applyToApkVariant$1$1': error writing value of type 'com.android.build.gradle.internal.api.ApkVariantOutputImpl'
            > Configuration cache state could not be cached: field 'services' from type 'com.android.build.gradle.internal.api.ApkVariantOutputImpl': error writing value of type 'com.android.build.gradle.internal.services.BaseServicesImpl'
               > Configuration cache state could not be cached: field 'projectServices' from type 'com.android.build.gradle.internal.services.BaseServicesImpl': error writing value of type 'com.android.build.gradle.internal.services.ProjectServices'
                  > Configuration cache state could not be cached: field 'buildServiceRegistry' from type 'com.android.build.gradle.internal.services.ProjectServices': error writing value of type 'org.gradle.api.services.internal.DefaultBuildServicesRegistry'
                     > Configuration cache state could not be cached: field 'isolatableFactory' from type 'org.gradle.api.services.internal.DefaultBuildServicesRegistry': error writing value of type 'org.gradle.internal.snapshot.impl.DefaultValueSnapshotter'
                        > Configuration cache state could not be cached: field 'isolatableValueVisitor' from type 'org.gradle.internal.snapshot.impl.DefaultValueSnapshotter': error writing value of type 'org.gradle.internal.snapshot.impl.DefaultValueSnapshotter$IsolatableVisitor'
                           > Configuration cache state could not be cached: field 'classLoaderHasher' from type 'org.gradle.internal.snapshot.impl.DefaultValueSnapshotter$IsolatableVisitor': error writing value of type 'org.gradle.groovy.scripts.internal.RegistryAwareClassLoaderHierarchyHasher'
                              > Configuration cache state could not be cached: field 'classLoaderFactory' from type 'org.gradle.groovy.scripts.internal.RegistryAwareClassLoaderHierarchyHasher': error writing value of type 'org.gradle.internal.classloader.DefaultHashingClassLoaderFactory'
                                 > Configuration cache state could not be cached: field 'classpathHasher' from type 'org.gradle.internal.classloader.DefaultHashingClassLoaderFactory': error writing value of type 'org.gradle.api.internal.initialization.loadercache.DefaultClasspathHasher'
                                    > Configuration cache state could not be cached: field 'fingerprinter' from type 'org.gradle.api.internal.initialization.loadercache.DefaultClasspathHasher': error writing value of type 'org.gradle.internal.fingerprint.classpath.impl.DefaultClasspathFingerprinter'
                                       > Configuration cache state could not be cached: field 'fileCollectionSnapshotter' from type 'org.gradle.internal.fingerprint.classpath.impl.DefaultClasspathFingerprinter': error writing value of type 'org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter'
                                          > Configuration cache state could not be cached: field 'genericFileTreeSnapshotter' from type 'org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter': error writing value of type 'org.gradle.internal.fingerprint.impl.DefaultGenericFileTreeSnapshotter'
                                             > Configuration cache state could not be cached: field 'hasher' from type 'org.gradle.internal.fingerprint.impl.DefaultGenericFileTreeSnapshotter': error writing value of type 'org.gradle.api.internal.changedetection.state.CachingFileHasher'
                                                > Configuration cache state could not be cached: field 'cache' from type 'org.gradle.api.internal.changedetection.state.CachingFileHasher': error writing value of type 'org.gradle.cache.internal.CrossProcessSynchronizingCache'
                                                   > Configuration cache state could not be cached: field 'cacheAccess' from type 'org.gradle.cache.internal.CrossProcessSynchronizingCache': error writing value of type 'org.gradle.cache.internal.LockOnDemandCrossProcessCacheAccess'
                                                      > Configuration cache state could not be cached: field 'fileLock' from type 'org.gradle.cache.internal.LockOnDemandCrossProcessCacheAccess': error writing value of type 'org.gradle.cache.internal.DefaultFileLockManager$DefaultFileLock'
                                                         > Configuration cache state could not be cached: field 'this$0' from type 'org.gradle.cache.internal.DefaultFileLockManager$DefaultFileLock': error writing value of type 'org.gradle.cache.internal.DefaultFileLockManager'
                                                            > Configuration cache state could not be cached: field 'fileLockContentionHandler' from type 'org.gradle.cache.internal.DefaultFileLockManager': error writing value of type 'org.gradle.cache.internal.locklistener.DefaultFileLockContentionHandler'
                                                               > Configuration cache state could not be cached: field 'addressFactory' from type 'org.gradle.cache.internal.locklistener.DefaultFileLockContentionHandler': error writing value of type 'org.gradle.internal.remote.internal.inet.InetAddressFactory'
                                                                  > Configuration cache state could not be cached: field 'communicationAddresses' from type 'org.gradle.internal.remote.internal.inet.InetAddressFactory': error writing value of type 'java.util.ArrayList'
                                                                     > 'ObjectOutputStream.putFields' is not supported by the Gradle configuration cache.

Thanks for the report, but this output is nearly meaningless to me. Can you point to where, in this huge pile of nested errors, the actual problem might lie? Most of this seems to be Gradle internals. I've no doubt that this plugin is doing something wrong, but it's hard to say what.

Also, FYI AGP 4.1.0 (as yet unreleased) will be supported properly in the next dexcount release.

Kotlin 1.4 (also unreleased) is out-of-scope at present.

If I had to guess it seems like this error is because we capture an ApkVariantOutput instance in a closure when creating a Provider<RegularFile>, and passing that provider as input to a task (https://github.com/KeepSafe/dexcount-gradle-plugin/blob/master/src/main/kotlin/com/getkeepsafe/dexcount/Plugin.kt#L383).

This is necessary here because the value at configuration time can change, if the user renames their APK. Unfortunately in AGP 3.x the output filename isn't available in a Property or Provider. Fixing this issue here in the obvious way means breaking dexcount for renamed APKs, and I don't feel like it's a worthwhile tradeoff to break a prod usecase to accommodate an unreleased (i.e. still incubating) Gradle feature.

I'll take this into account for AGP 4.1 support, but will probably keep configuration-cache unsupported for earlier AGPs unless we find a way to preserve existing features.

...of course I might be misreading the error chain. If that's so, please let me know.

With the merge of #305, I believe this should work for your test case. @CristianGM Please try the 2.0.0-SNAPSHOT build (see the readme for instructions on how to get snapshots) and let me know whether that works.

I'll try it today. Thanks!

I had in my to-do list to add more details to the issue because I had the same suspicion, but it didn't really make sense to me, as at the end what the task receives is the RegularFile, but also if it fails transforming the property it could explain the weird error.

It does work perfectly running: ./gradlew :app:countReleaseDexMethods
(the readme doesn't mention that task and I was surprised it didn't do anything when running assembleRelease)

So we can close this issue.

A slightly unrelated question, why isn't countReleaseDexMethods cacheable (it's not a big deal, just curious)?

It does work perfectly running: ./gradlew :app:countReleaseDexMethods

Nice, thanks for verifying!

(the readme doesn't mention that task...

The plugin creates one task per variant, in the template count{variantName}DexMethods; what variants are there is specific to your build.gradle file, so I haven't documented anything but the most basic and common "Debug" variant.

...and I was surprised it didn't do anything when running assembleRelease)

See #302. TL;DR in AGP 4.1 and above, we don't have direct access to the tasks that produce the things to be counted, and because of the way Gradle works that means we can't (safely) make the counting task happen automatically. It's part of the reason I'm moving from 1.0.3 to 2.0.0, because this is definitely a breaking change.

A slightly unrelated question, why isn't countReleaseDexMethods cacheable (it's not a big deal, just curious)?

My understanding of "cacheable" is that if task inputs haven't changed, then the task is up-to-date and isn't run. That's true of count${variant}DexMethods today. What are you referring to?

Oh, right, that kind of cacheable (the build cache). It used to be cacheable but folks complained that they didn't see method counts in stdout when they wanted them; if the task is "from cache", then it isn't run and nothing is printed. This is contra most people's product expectations, so we dropped build-cache support.

So, they loose the stdout only when is running on the same machine because up-to-date checks, but don't if they change because it's not FROM-CACHE.

I always though that this kind of tasks that need to do some heavy work + print should be split in 2 tasks. The first, and cacheable, do the heavy work and output the report. The second and not cacheable do read the report and print, so it's fast and doesn't even need to have up-to-date checks.

In any case, that's a different discussion. ๐Ÿ‘

That's a great idea and I'm kind of shocked that in five years it never occurred to me ๐Ÿ˜…
Thanks, I'll consider that.