Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Included builds containing build logic prevent configuration-cache reuse #21

Open
scana opened this issue Feb 5, 2024 · 9 comments
Open
Labels
bug Something isn't working

Comments

@scana
Copy link

scana commented Feb 5, 2024

Hi!

Just tried out Gradle 8.6 with gradle/actions/setup-gradle@v3 and it seems like a build-conventions (https://docs.gradle.org/current/samples/sample_sharing_convention_plugins_with_build_logic.html) related task input change is preventing my configuration-cache from being reused.
They do seem to cache fine on local builds.

Calculating task graph as configuration cache cannot be reused because 
an input to task ':build-conventions:generatePluginAdapters' has changed.

Sample job that I run:

- name: Run ktlint
  uses: gradle/actions/setup-gradle@v3
  with:
    gradle-home-cache-cleanup: true
    cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
    arguments: ktlintCheck

Is there any chance this might be related to gradle-home-cache-cleanup #19 ?
If not, is there anything else that we should cache?

I suppose build conventions falls under this category:

caches/<version>/kotlin-dsl and caches/<version>/scripts: These are the compiled build scripts. The Kotlin ones in particular can benefit from caching.

@bigdaz
Copy link
Member

bigdaz commented Feb 5, 2024

Thanks for the report. The configuration-cache hasn't been designed to be reused on different machines, but with gradle-build-action and the Gradle User Home fully restored we've found that it often works. Unfortunately you seem to have found a case where it doesn't.

The error message indicates that GRADLE_ENCRYPTION_KEY is being set correctly and the configuration-cache is being properly restored. But something in the project state isn't as expected after restore, invalidating the cache entry.

I suspect that this is due to the following process:

  • When your project is configured, you have an included build build-conventions that must be built in order to provide some build logic.
  • This results in the inputs to task :build-convention:generatePluginAdapters being marked as configuration-cache inputs.
    • The generatePluginAdapters task likely has some inputs that are actually the outputs of other build-conventions tasks, and are located in the build-conventions/build directory.
  • When your workflow runs, the gradle-build-action will restore the Gradle User Home together with the configuration-cache entry. But it will not restore the build-conventions/build directory. This results in the "configuration cache cannot be reused" message you see.

You could validate this locally by:

  1. Run the build locally, storing the configuration-cache entry
  2. Clean the local build, including any included-builds. This might require manual deletion of the build directories.
  3. Run the build again and see if the configuration-cache can be reused.

@scana
Copy link
Author

scana commented Feb 5, 2024

Thank you for clarifying this @bigdaz.
I did play with cleaning up parts of the project in order to further investigate this.
Perhaps there's a smarter way to do it but I did try removing directories one by one.

Removing build-conventions/build results in:

Calculating task graph as configuration cache cannot be reused because 
an input to plugin 'org.gradle.groovy-gradle-plugin' has changed.

Just build-conventions/build/pluginDescriptors:

Calculating task graph as configuration cache cannot be reused because 
an input to task ':build-conventions:processResources' has changed.

build-conventions/build/resources:

Calculating task graph as configuration cache cannot be reused because 
an input to task ':build-conventions:jar' has changed.

build-conventions/build/classes:

Calculating task graph as configuration cache cannot be reused because 
an input to task ':build-conventions:compileGroovyPlugins' has changed.

And finally build-conventions/build/groovy-dsl-plugins/output/plugin-requests gives me the message that I'm seeing on CI:

Calculating task graph as configuration cache cannot be reused because 
an input to task ':build-conventions:generatePluginAdapters' has changed.

Is this something could perhaps be cached on the side? Not sure if setup-gradle action takes care of local build files other than those located in Gradle home dir?

@bigdaz
Copy link
Member

bigdaz commented Feb 6, 2024

The setup-gradle task doesn't (currently) take care of any of this, so you'll need to configure this yourself. The simplest way is probably using actions/cache to save/restore the build-conventions/build directory.

If you get this working, please share the configuration for others.

@scana
Copy link
Author

scana commented Feb 8, 2024

Didn't get this to fully work yet, but thought it might be worth sharing what I've tried already.

- name: Cache build logic
  uses: actions/cache@v4
  with:
    path: build-conventions/build/groovy-dsl-plugins/output/plugin-requests
    key: build-logic-${{ hashFiles('build-conventions/**/*.gradle', 'gradle/libs.versions.toml') }}

Results in:

Calculating task graph as configuration cache cannot be reused because an input to task ':build-conventions:compileJava' has changed.

Changing path to build-conventions/build/ either successfully reuses the configuration or fails with:

Calculating task graph as configuration cache cannot be reused because an input to plugin 'org.gradle.groovy-gradle-plugin' has changed.

I have also encountered an error right now when trying to run a build, but I suppose this could be a separate issue?

* What went wrong:
Could not load the value of field `parameters` of `org.gradle.api.internal.provider.DefaultValueSourceProviderFactory$DefaultObtainedValue` bean found in field `obtainedValue` of `org.gradle.configurationcache.fingerprint.ConfigurationCacheFingerprint$ValueSource` bean found in Gradle runtime.
> com.android.build.gradle.internal.services.AndroidLocationsBuildService$AndroidDirectoryCreator$Params

Additional references:

@bigdaz
Copy link
Member

bigdaz commented Feb 8, 2024

The best way to solve this is likely to experiment locally. Something like:

./gradlew help
rm -rf build-conventions/build
./gradlew help

The second build should reproduce the configuration cache issue you're seeing on CI. You can then experiment to see exactly what needs to be restored. I suspect the entire build-conventions/build directory will be required.

Also, the cache key should probably include all source files under build-conventions. Any change to these sources could change the outputs in build, and these are required to reuse the configuration cache.

@bigdaz bigdaz changed the title build-conventions caching Included builds containing build logic prevent configuration-cache reuse Feb 9, 2024
@bigdaz
Copy link
Member

bigdaz commented Feb 9, 2024

Another option to solve this would be to add a separate ./gradlew :build-conventions:assemble --no-configuration-cache step prior to your main build step. This should ensure that all included build logic is available when reusing the configuration cache.

@bigdaz bigdaz transferred this issue from gradle/gradle-build-action Feb 9, 2024
@bigdaz bigdaz added the bug Something isn't working label Feb 9, 2024
@hfhbd
Copy link
Contributor

hfhbd commented Apr 8, 2024

I have a small pet project that never reuses the CC, so I added the build logic directory manually: https://github.com/hfhbd/adventOfCode/pull/29/files and it does work now! So it would be nice, if this action would store the this directory (or at least the relevant parts) by default.

@ansman
Copy link

ansman commented Sep 11, 2024

Unfortunately, simply building the included build isn't enough to make CC work. You get errors like this when CC is being reused:

Could not load the value of field `parameters` of `org.gradle.api.internal.provider.DefaultValueSourceProviderFactory$DefaultObtainedValue` bean found in field `obtainedValue` of `org.gradle.internal.cc.impl.fingerprint.ConfigurationCacheFingerprint$ValueSource` bean found in Gradle runtime.
> Failed to instrument class org/jetbrains/kotlin/gradle/plugin/internal/CustomPropertiesFileValueSource$Parameters in ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:]:buildSrc[:]:root-project[:](export)}

We have a fairly intricate setup to make CC work on CI which (roughly) works like this:

  1. Restore caches
  2. Check if we got a CC hit (we use the existence of included-build/build as a marker for this)
  • If we did not get CC hit, do:
    a. ./gradlew <task> --dry-run
    b. Cache included-build/build and ~/.gradle/caches/<version>/transforms
  • If we got a hit, do nothing
  1. Run ./gradlew <task>
  2. Cache the the following directories:
  • ~/.gradle/caches/modules-*/files-*
  • ~/.gradle/caches/jars-*
  • ~/.gradle/wrapper
  • `~/.gradle/caches//(cc-keystore|kotlin-dsl|generated-gradle-jars|dependencies-accessors)
  • .gradle/<version>/dependencies-accessors
  • .gradle/configuration-cache

This is far from idea because we end up loading CC one extra time in step 3. when we get a cache miss but the savings still outweigh the cost.

The reason for doing this (and the reason why the standard action doesn't work and why simply building the included build doesn't work) is:

  • You need to back up the transforms and dependencies. If you don't you'll get sporadic CC errors like the one above.
    • Backing up the transforms after step 3. would include transforms for the main build which severely bloats the cache size.
  • It appears that configuring the task graph writes data that is needed for CC to work, this data is not written by simply building the included build.

I really hope Gradle makes this work on CI as we were able to cut out p50 build times in half when we got CC to work.

@hfhbd
Copy link
Contributor

hfhbd commented Oct 13, 2024

What about the default includedBuild buildSrc? IMOH, this action should cache the build dir (and anything else needed) of buildSrc by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants