Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Laptop Builds to Advanced CI

From Laptop Builds to Advanced CI

How do you transition from the solo-coder mindset to building a robust, automated CI pipeline that supercharges your team?

Once you get a basic pipeline running there are numerous aspects to evaluate:

* Are the results useful?
* You start to add unit test results and collect build artifacts. Is it fast and reliable enough that the team benefits from it?
* Do you need a merge queue or do you need better checks?
* What is consistently missed that you can automate?
* What tedious repetitive tasks can you transform into a welcome resource?
* Are you hitting resource limits that require an engineering investment to mitigate?
* What JVM options are right for your codebase?
* Is Docker useful? How far should you go in your optimizations?
* How do you secure the pipeline?

We'll delve into each topic and share how to apply our learnings to empower you. Along the way we will discuss how to approach stakeholders outside engineering to demonstrate the value it brings to a business. Join us for a saga of struggles and victories and how we transformed our CI pipeline at a modern scale-up business.

Jason Pearson

October 28, 2023
Tweet

More Decks by Jason Pearson

Other Decks in Technology

Transcript

  1. From Laptop Builds to Advanced CI Oct 27th, 2023 Jason

    Pearson
 Senior Staff Engineer Formerly Hinge
  2. CI experience since 2011. Built Android at Hinge, went from

    solo dev to 24 Android engineers. Also spent years in backend and fullstack roles across various companies.
  3. Make it Faster 12 9 6 3 0 Unit Tests

    Release R8 UI Tests Unit Tests Release R8 UI tests minutes
  4. ~400 Modules 💻 290k lines of Kotlin 24 active contributors

    Context Dagger Hilt (KAPT) 🗡 Gradle Kotlin Scripts KSP (except Dagger)
  5. Problems 🤯 Distracting from Feature Work ❌ Regressions 🏴☠ Security

    Concerns CI Solutions 🏗 Automate Building & Publishing 👩🔬 Automate Tests 🔐 Automate Security
  6. When do we build? What do we run? Self hosted

    or Managed? What should CI look like?
  7. version: 2.1 jobs: unit_tests: docker: - image: circleci/android:api-34-node steps: -

    checkout - run: ./gradlew testDebugUnitTest config.yml 🚧
  8. * What went wrong: Execution failed for task ':features:user-account:fresh- start:testInternalDebugUnitTest'.

    > There were failing tests. See the report at: file:///mnt/ramdisk/ features/user-account/fresh-start/build/reports/tests/ testInternalDebugUnitTest/index.html * Exception is: org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':features:user-account:fresh-start:testInternalDebugUnitTest'. at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$e xecuteIfValid$1(ExecuteActionsTaskExecuter.java:148) at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeI fValid(ExecuteActionsTaskExecuter.java:146) at 🚧
  9. * What went wrong: Execution failed for task ':features:user-account:fresh- start:testInternalDebugUnitTest'.

    > There were failing tests. See the report at: file:///mnt/ramdisk/ features/user-account/fresh-start/build/reports/tests/ testInternalDebugUnitTest/index.html * Exception is: org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':features:user-account:fresh-start:testInternalDebugUnitTest'. at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$e xecuteIfValid$1(ExecuteActionsTaskExecuter.java:148) at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeI fValid(ExecuteActionsTaskExecuter.java:146) at 🚧
  10. * What went wrong: Execution failed for task ':features:user-account:fresh- start:testInternalDebugUnitTest'.

    > There were failing tests. See the report at: file:///mnt/ramdisk/ features/user-account/fresh-start/build/reports/tests/ testInternalDebugUnitTest/index.html * Exception is: org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':features:user-account:fresh-start:testInternalDebugUnitTest'. at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$e xecuteIfValid$1(ExecuteActionsTaskExecuter.java:148) at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeI fValid(ExecuteActionsTaskExecuter.java:146) at 🚧
  11. version: 2.1 jobs: unit_tests: docker: - image: circleci/android:api-34-node steps: -

    checkout - run: ./gradlew testDebugUnitTest config.yml 🚧
  12. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - store_test_results: path: ~/test-results config.yml 🚧
  13. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | when: always - store_test_results: path: ~/test-results config.yml 🚧
  14. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ when: always - store_test_results: path: ~/test-results config.yml 🚧
  15. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ find . when: always - store_test_results: path: ~/test-results config.yml 🚧
  16. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ find . \ -type f when: always - store_test_results: path: ~/test-results config.yml 🚧
  17. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ find . \ -type f \ -regex “.*/build/test-results/.*xml when: always - store_test_results: path: ~/test-results config.yml 🚧
  18. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ find . \ -type f \ -regex “.*/build/test-results/.*xml” \
 -exec cp {} when: always - store_test_results: path: ~/test-results config.yml 🚧
  19. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ find . \ -type f \ -regex “.*/build/test-results/.*xml” \
 -exec cp {} ~/test-results/junit/ \; when: always - store_test_results: path: ~/test-results config.yml 🚧
  20. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ find . \ -type f \ -regex “.*/build/test-results/.*xml” \
 -exec cp {} ~/test-results/junit/ \; when: always - store_test_results: path: ~/test-results config.yml 🚧
  21. unit_tests: docker: - image: circleci/android:api-34-node steps: - checkout - run:

    ./gradlew testDebugUnitTest - run: name: Save Test Results command: | mkdir -p ~/test-results/junit/ find . \ -type f \ -regex “.*/build/test-results/.*xml” \
 -exec cp {} ~/test-results/junit/ \; when: always - store_test_results: path: ~/test-results config.yml ✅
  22. version: 2.1 jobs: ui_tests: docker: - image: circleci/android:api-34-node steps: -

    checkout - run: ./gradlew connectedDebugAndroidTest config.yml ✅ 🚧
  23. version: 2.1 jobs: ui_tests: docker: - image: circleci/android:api-34-node steps: -

    checkout - run: ./gradlew connectedDebugAndroidTest config.yml ❌ ✅ 🚧
  24. But how should we automate UI tests? Don’t write them

    to begin with Use a fast vendor OR
  25. ᐅ BikeShed > ./gradlew tasks > Task :tasks ------------------------------------------------------------ Tasks

    runnable from root project 'BikeShed' ------------------------------------------------------------ Android tasks ------------- androidDependencies - Displays the Android dependencies of the project. signingReport - Displays the signing info for the base and test modules sourceSets - Prints out all the source sets defined in this project. Build tasks ----------- assemble - Assemble main outputs for all the variants. assembleAndroidTest - Assembles all the Test applications. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildKotlinToolingMetadata - Build metadata json file containing information about the used Kotlin tooling buildNeeded - Assembles and tests this project and all projects it depends on.
  26. Android tasks ------------- androidDependencies - Displays the Android dependencies of

    the project. signingReport - Displays the signing info for the base and test modules sourceSets - Prints out all the source sets defined in this project. Build tasks ----------- assemble - Assemble main outputs for all the variants. assembleAndroidTest - Assembles all the Test applications. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildKotlinToolingMetadata - Build metadata json file containing information about the used Kotlin tooling buildNeeded - Assembles and tests this project and all projects it depends on. bundle - Assemble bundles for all the variants. clean - Deletes the build directory. compileDebugAndroidTestSources compileDebugSources compileDebugUnitTestSources compileReleaseSources compileReleaseUnitTestSources Build Setup tasks ----------------- init - Initializes a new Gradle build. wrapper - Generates Gradle wrapper files.
  27. Android tasks ------------- androidDependencies - Displays the Android dependencies of

    the project. signingReport - Displays the signing info for the base and test modules sourceSets - Prints out all the source sets defined in this project. Build tasks ----------- assemble - Assemble main outputs for all the variants. assembleAndroidTest - Assembles all the Test applications. bundle - Assemble bundles for all the variants.
  28. version: 2.1 jobs: build: docker: - image: circleci/android:api-34-node steps: -

    checkout - run: ./gradlew assembleDebug - run: ./gradlew assembleDebugAndroidTest - run: scripts/upload_and_test.sh config.yml 👩🔬
  29. version: 2.1 jobs: publish: docker: - image: circleci/android:api-34-node steps: -

    checkout - run: ./gradlew bundleRelease - run: ./gradlew publishBundle config.yml
  30. version: 2.1 jobs: publish: docker: - image: circleci/android:api-34-node steps: -

    checkout - run: | openssl aes-256-cbc -d \ -in bikeshed-keystore-secret \ -out ./app/bikeshed.keystore \ -k $SECRET_KEY - run: ./gradlew bundleRelease - run: ./gradlew publishBundle config.yml
  31. Static Analysis ktlint to ensure consistency in writing detekt for

    code smells xmlstarlet to validate XML structure
  32. 🏗 Automate Building & Publishing 👩🔬 Automate Tests 🔐 Automate

    Security “But why is it taking so long?” ⏰
  33. 🏗 Automate Building & Publishing 👩🔬 Automate Tests 🔐 Automate

    Security “But why is it taking so long?” ⏰
  34. 🏗 Automate Building & Publishing 👩🔬 Automate Tests 🔐 Automate

    Security ⏰ 📦 Caching 🏭 Parallelization Merge Queue 🥷 Shadow Jobs Original Problems Scale Problems
  35. This is what we want 12 9 6 3 0

    Unit Tests Release R8 UI Tests Unit Tests Release R8 UI tests minutes
  36. 12 9 6 3 0 Unit Tests Release R8 UI

    Tests Unit Tests Release R8 UI tests minutes How do we get to this?
  37. 12 9 6 3 0 Unit Tests Release R8 UI

    Tests Unit Tests Release R8 UI tests minutes How do we get to this?
  38. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker 👩🔬 Unit Tests on CI
  39. 💻 Local Support Gradle Caches Build Cache 💻 💻 💻

    Configuration Cache Dependency Cache Script Cache Local Support Local Support Local Support
  40. 💻 Local Support Gradle Caches Build Cache 💻 💻 💻

    Configuration Cache Dependency Cache Script Cache Local Support Local Support Local Support
  41. Gradle Cache directories ~/.gradle ├── caches │ ├── 8.1 │

    ├── 8.2 │ ├── ⋮ │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 │ ├── modules-2 │ └── transforms-3 │ ├── ⋮
  42. Gradle Cache directories ~/.gradle ├── caches │ ├── 8.1 │

    ├── 8.2 │ ├── ⋮ │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 │ ├── modules-2 │ └── transforms-3 │ ├── ⋮ Dependency Cache
  43. Gradle Cache directories ~/.gradle ├── caches │ ├── 8.1 │

    ├── 8.2 │ │ ├── generated-gradle-jars │ │ └── kotlin-dsl │ ├── ⋮ │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 │ ├── modules-2 │ └── transforms-3 │ Dependency Cache Script Cache
  44. 💻 Local Support Gradle Caches Build Cache 💻 💻 💻

    Configuration Cache Dependency Cache Script Cache Local Support Local Support Local Support
  45. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  46. Control Cache build dependencies 40 30 20 10 0 Partial

    Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  47. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change Cache build dependencies minutes cache save gradle execution gradle configuration cache restore git Docker
  48. # If you don't turn these on you aren't #

    getting any performance wins from # Gradle Build Cache or modularization. org.gradle.caching=true org.gradle.parallel=true gradle.properties
  49. # Kotlin 1.7+ only # Good reason to upgrade if

    you are on # an older version. kotlin.incremental.useClasspathSnapshot =true gradle.properties
  50. # Incremental compilation with KAPT. # Only if you still

    use KAPT. kapt.incremental.apt =true gradle.properties
  51. # Stop generating BuildConfig Java stubs. # If this is

    the last Java in your codebase # you could see a decent performance win. android.enableBuildConfigAsBytecode =true gradle.properties
  52. # Stop generating BuildConfig files # if you don't use

    them. android.defaults.buildfeatures.buildconfig =false gradle.properties
  53. # Experimental flag for parallelizing # R8 work - only

    useful if you have # multiple R8 tasks. android.r8.maxWorkers =2 gradle.properties
  54. Gradle Properties Other engineers ask me, "Where do you find

    these?" github.com/androidx/androidx and other OSS projects
  55. # Re-renables Android Cache fix plugin for currently disabled defaults

    we believe are safe for a speed boost. systemProp.org.gradle.android.cache- fix.MergeSourceSetFolders.caching.enabled=true systemProp.org.gradle.android.cache- fix.ZipMergingTask.caching.enabled=true gradle.properties
  56. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change Cache build dependencies minutes cache save gradle execution gradle configuration cache restore git Docker
  57. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change Cache build dependencies minutes cache save gradle execution gradle configuration cache restore git Docker
  58. Overhead Cost 📦 📦 📦 📦 Depending on how you

    organize your CI jobs these costs can become significant. 🏭 Parallelization
  59. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  60. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  61. Gradle Build Cache - Modularize • Reduce the number of

    inputs per task and make them more stable • Watch Siggi’s talk about graph theory • Gradle build scans to observe performance
  62. Gradle Remote Cache • Do it: https://docs.gradle.com/build-cache-node/ • 90 Days,

    300GB, use validation scripts • Most local and CI runs will benefit from build cache hits • Free
  63. • Provides support for features beyond remote cache • Really

    helps figure out failures, cache misses, and performance issues across a team • Looking forward to using test prediction
  64. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Local Support 💻 Local Support Remote Support Remote Support
  65. Gradle Caches Build Cache 💻 Configuration Cache Dependency Cache Script

    Cache Remote Support Local Support Remote Support Remote Support
  66. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker Gradle Build Cache Optimization
  67. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker Gradle Build Cache Optimization
  68. Control JDK 11 -> JDK 19 40 30 20 10

    0 Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  69. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker JDK 11 -> JDK 19
  70. 🥷 Shadow Jobs Run nightly checks against your project with

    latest snapshots of new versions. “Will version X of dependency Y work on our project?” Zac Sweers
  71. 🥷 Shadow Jobs Run nightly checks against your project with

    latest snapshots of new versions. “Will version X of dependency Y work on our project?” Zac Sweers
  72. JVM Argument Tuning DISCLAIMER: I am not accountable for any

    of these settings. If they don’t work for you there are so many reasons why your setup would handle them differently.
  73. Garbage Collection Algo Tradeoff Each JVM GC algorithm makes a

    choice about what they optimize for and how much.
  74. ParallelGC Garbage Collection Algo Tradeoff G1GC • Optimize for speed

    • Maximum footprint • Might have longer GC pauses • Balance throughput • Lower footprint • Shorter GC pauses
  75. ParallelGC Garbage Collection Algo Tradeoff G1GC • Optimize for speed

    • Maximum footprint • Might have longer GC pauses • Balance throughput • Lower footprint • Shorter GC pauses Use when resource constrained Use when resources allow
  76. kotlin.daemon.jvm.options When passing arguments, follow these rules: Use the minus

    sign - only before the arguments Xmx, XX:MaxMetaspaceSize, and XX:ReservedCodeCacheSize. Separate arguments with commas (,) without spaces. Arguments that come after a space will be used for the Gradle daemon, not for the Kotlin daemon. Kotlin JVM Arguments
  77. kotlin.daemon.jvm.options When passing arguments, follow these rules: Use the minus

    sign - only before the arguments Xmx, XX:MaxMetaspaceSize, and XX:ReservedCodeCacheSize. Separate arguments with commas (,) without spaces. Arguments that come after a space will be used for the Gradle daemon, not for the Kotlin daemon. Kotlin JVM Arguments
  78. kotlin.daemon.jvm.options When passing arguments, follow these rules: Use the minus

    sign - only before the arguments Xmx, XX:MaxMetaspaceSize, and XX:ReservedCodeCacheSize. Separate arguments with commas (,) without spaces. Arguments that come after a space will be used for the Gradle daemon, not for the Kotlin daemon. Kotlin JVM Arguments
  79. kotlin.daemon.jvmargs You can add the kotlin.daemon.jvmargs property in the gradle.properties

    fi le: kotlin.daemon.jvmargs=-Xmx1500m -Xms=500m Kotlin JVM Arguments
  80. kotlin.daemon.jvmargs You can add the kotlin.daemon.jvmargs property in the gradle.properties

    fi le: kotlin.daemon.jvmargs=-Xmx1500m -Xms=500m Using spaces at the beginning will cause all arguments to be ignored Kotlin JVM Arguments
  81. Xmx (max) JVM Heap Size Xms (min) • Amount of

    memory it takes to compile your biggest task / module. • For most projects this is going to be your main app module’s minification step. • Somewhere between 80 and 100% of Xmx. • Heap only grows when GC happens, so increasing it avoids this penalty.
  82. MaxMetaspaceSize Metaspace CompressedClassSpaceSize • class & non-class space • unlimited

    by default • virtual size of compressed classes • defaults to 1GB
  83. CompressedOops Metaspace CompressedClassPointers • Defaults on < 30GB heap •

    Uses 4 fewer bytes • Restricts memory heap to 32GB • Available if CompressedOops is on
  84. Metaspace CompressedOops & unlimited MaxMetaspace is going to be better

    for majority of Android projects. (so stick with the defaults)
  85. SoftRefLRUPolicyMSPerMB The number of milliseconds to hold onto soft references

    per MB of heap size, default 1000 = 2h 16m 32s 8GB Heap @ 1000 MS Policy
  86. SoftRefLRUPolicyMSPerMB The number of milliseconds to hold onto soft references

    per MB of heap size, default 1000 = 2h 16m 32s 8GB Heap @ 1000 MS Policy 8GB Heap @ 10 MS Policy = 0h 1m 22s
  87. SoftRefLRUPolicyMSPerMB The number of milliseconds to hold onto soft references

    per MB of heap size, default 1000 = 2h 16m 32s 8GB Heap @ 1 MS Policy 8GB Heap @ 1000 MS Policy = 0h 0m 8s 8GB Heap @ 10 MS Policy = 0h 1m 22s
  88. Gradle Max Workers --max-workers The maximum number of active workers

    to allow Should probably be number of performance cores, possibly 1-2 less Will not spawn more workers is memory heap requirements cannot be met
  89. malloc alternatives Therefore use jemalloc (or tcmalloc) to be able

    to run more daemons in parallel with less memory pressure.
  90. 🥷 Shadow Jobs 1. Instrument your build with JVM args

    as parameters 2. Run parameterized tests 3. Profit What do we actually pick for JVM args?
  91. • GC Algo = It Depends (try for Parallel) •

    SoftRefLRUPolicyMSPerMB = 1 • CodeCache = 256MB - 512MB • MaxMetaspace = Some GB or unlimited • Metaspace = 64MB - 1GB • Xmx = 4GB - 8GB • Xms = 3GB - 7GB • Max Workers=Performance Cores - 2 What do we actually pick?
  92. • jcmp for Metaspace • –XX:+PrintCodeCache for CodeCache • Measure

    CPU & Memory resource usage, lots of tools available JVM Measurement Tools
  93. Control JVM Optimizations 40 30 20 10 0 Partial Cache

    Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  94. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker JVM Optimizations
  95. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker JVM Optimizations
  96. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support 💻 Local Support
  97. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support 💻 Local Support
  98. Gradle Cache directories ~/.gradle ├── caches │ └── ⋮ ~/project

    ├── .gradle ├── app ├── build ├── gradle └── ⋮ Dependency Cache Script Cache
  99. Gradle Cache directories ~/project ├── .gradle │ │ │ │

    ├── app ├── build ├── gradle └── ⋮
  100. Gradle Cache directories ~/project ├── .gradle │ ├── 8.1 │

    ├── 8.2 │ ├── ⋮ │ └── configuration-cache ├── app ├── build ├── gradle └── ⋮
  101. ~/project ├── .gradle │ ├── 8.1 │ ├── 8.2 │

    ├── ⋮ │ └── configuration-cache ├── app ├── build ├── gradle └── ⋮ Configuration Cache Gradle Cache directories
  102. As of Gradle 8.1 encryption was added. Asking Gradle support

    - configuration cache on CI is unsupported Gradle Configuration Cache on CI
  103. As of Gradle 8.1 encryption was added. Gradle's response has

    been that configuration cache on CI is not supported Gradle Configuration Cache on CI
  104. As of Gradle 8.1 encryption was added. Gradle's response has

    been that configuration cache on CI is not supported. But we persisted anyway... Gradle Configuration Cache on CI
  105. Gradle Configuration Cache on CI Environment Variable Environment Variables Prefixed

    File system Entry File System property Value from custom source 35 1 2007 4 52 5 build/reports/configuration-cache/4tnz..i67j/27o6..b0jl/configuration-cache-report.html We looked at the reports for all inputs
  106. But despite everything 100% of inputs being consistent we continued

    to get an unhelpful error message: Gradle Configuration Cache on CI
  107. Gradle Configuration Cache on CI Calculating task graph as no

    configuration cache is available for tasks
  108. Gradle Configuration Cache on CI “You need to cache the

    encryption key.” — Inaki Villar
  109. Gradle Configuration Cache on CI Caveat to this method -

    you will be caching any secrets used by this build, so either: • Only use this for builds that do not rely on secrets • Knowingly store this with the security risk
  110. Gradle User Home directory ~/.gradle ├── caches │ ├── 8.1

    │ │ … │ ├── 8.4 │ │ └── cc-keystore │ │ ├── cache.properties │ │ ├── cc-keystore.lock │ │ └── gradle.keystore │ ├── │ ├── buildcache—1 │ ├── jars-9 │ ├── journal-1 Configuration Cache Encryption
  111. Gradle Configuration Cache on CI Calculating task graph as no

    configuration cache is available for tasks
  112. unit_tests: docker: - image: circleci/android:api-34-node steps: - run: name: Clean

    Gradle Cache command: | find . -type f -name '*.lock' -delete find . -type f -name 'gc.properties' -delete find ~/.gradle -type f -name '*.lock' -delete find ~/.gradle -type f -name 'gc.properties' -delete when: always - save_cache: paths: - ~/.gradle/caches/<< parameters.gradle_version >>/ generated-gradle-jars - ~/.gradle/caches/<< parameters.gradle_version >>/ kotlin-dsl - .gradle/configuration-cache - ~/.gradle/caches/<< parameters.gradle_version >>/ cc-keystore - .gradle/<< parameters.gradle_version >> config.yml
  113. unit_tests: docker: - image: circleci/android:api-34-node steps: - run: name: Clean

    Gradle Cache command: | find . -type f -name '*.lock' -delete find . -type f -name 'gc.properties' -delete find ~/.gradle -type f -name '*.lock' -delete find ~/.gradle -type f -name 'gc.properties' -delete when: always - save_cache: paths: - ~/.gradle/caches/<< parameters.gradle_version >>/ generated-gradle-jars - ~/.gradle/caches/<< parameters.gradle_version >>/ kotlin-dsl - .gradle/configuration-cache - ~/.gradle/caches/<< parameters.gradle_version >>/ cc-keystore - .gradle/<< parameters.gradle_version >> config.yml 🤦
  114. Gradle Configuration Cache on CI Calculating task graph as configuration

    cache cannot be reused because an input to task ‘:gradle-plugin:generateExternalPluginSpecBuilders' has changed.
  115. Gradle Configuration Cache on CI Calculating task graph as configuration

    cache cannot be reused because an input to task ‘:gradle-plugin:generateExternalPluginSpecBuilders' has changed.
  116. “Gradle has compilation avoidance for non- ABI changes in included

    builds, but due to a bug any inline function would cause all build scripts to be recompiled even when there is a non-ABI change.” - Nicklas Ansman Gradle Configuration Cache on CI
  117. Gradle Configuration Cache on CI Could not load the value

    of field `patterns` of `org.gradle.configurationcache.serialization.codecs.DirectoryTree Spec` bean found in field `fileSystemInputs` of `org.gradle.configurationcache.fingerprint.ConfigurationCacheFing erprint$WorkInputs` bean found in Gradle runtime. > Failed to instrument class org/jetbrains/kotlin/gradle/tasks/ KotlinCompile$ScriptFilterSpec in ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:gradle- plugin]:buildSrc[:gradle-plugin]:root-project[:gradle-plugin] (export)}
  118. Gradle Configuration Cache on CI Could not load the value

    of field `patterns` of `org.gradle.configurationcache.serialization.codecs.DirectoryTree Spec` bean found in field `fileSystemInputs` of `org.gradle.configurationcache.fingerprint.ConfigurationCacheFing erprint$WorkInputs` bean found in Gradle runtime. > Failed to instrument class org/jetbrains/kotlin/gradle/tasks/ KotlinCompile$ScriptFilterSpec in ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:gradle- plugin]:buildSrc[:gradle-plugin]:root-project[:gradle-plugin] (export)}
  119. ~/.gradle/caches/<< gradle_version >>/generated-gradle-jars ~/.gradle/caches/<< gradle_version >>/kotlin-dsl ~/.gradle/caches/<< gradle_version >>/cc-keystore ~/.gradle/caches/modules-2

    ~/.gradle/caches/jars-9 ~/.gradle/caches/transforms-3 .gradle/<< gradle_version >> .gradle/configuration-cache gradle-plugin/build ci-cache.yml
  120. “Wow, I was prepared for a faster PR time but

    that was blazing” - Jacob Duron Gradle Configuration Cache on CI
  121. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support 💻 Local Support
  122. Gradle Caches Build Cache Configuration Cache Dependency Cache Script Cache

    Remote Support Remote Support Remote Support Remote Support
  123. Control Gradle Configuration Cache Reuse 40 30 20 10 0

    Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  124. Control Gradle Configuration Cache Reuse 40 30 20 10 0

    Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  125. 40 30 20 10 0 Gradle Configuration Cache Reuse Control

    Partial Cache Hit Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  126. Overhead Cost Optimizations 📦 git clone pulls 400 MB big

    images (2.5 GB) Minutes to save cache ~60 seconds to restore cache
  127. download & extract time 75 60 45 30 15 0

    seconds Circle/ Android JRE Bare Android
  128. download & extract time 75 60 45 30 15 0

    seconds Circle/ Android JRE Bare Android
  129. 500 400 300 200 100 0 default no blobs no

    trees shallow megabytes clone & checkout size
  130. 75 60 45 30 15 0 seconds default no blobs

    no trees shallow clone & checkout time
  131. 75 60 45 30 15 0 seconds default no blobs

    no trees shallow clone & checkout time
  132. Why did we go with treeless if its not the

    fastest? Shallow is faster, but it comes with a heavy penalty for any additional git operations clone & checkout time
  133. prune 40 30 20 10 0 Control Partial Cache Hit

    Cache Hit Core Change Cache Hit Leaf Change minutes cache save gradle execution gradle configuration cache restore git Docker
  134. Control 40 30 20 10 0 Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes prune cache save gradle execution gradle configuration cache restore git Docker
  135. 40 30 20 10 0 Control Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes clone optimization cache save gradle execution gradle configuration cache restore git Docker
  136. 40 30 20 10 0 Control Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes clone optimization cache save gradle execution gradle configuration cache restore git Docker
  137. 40 30 20 10 0 Control Partial Cache Hit Cache

    Hit Core Change Cache Hit Leaf Change minutes Present Day Unit Test Runs cache save gradle execution gradle configuration cache restore git Docker
  138. Present Day CI Runs 12 9 6 3 0 Unit

    Tests Release R8 UI Tests Unit Tests Release R8 UI tests minutes
  139. CI Speed Optimizations Cache Dependencies Cache Build Tasks Cache Configuration

    JVM Tuning Compiler Flags 📦 treeless or shallow clone prune images 🏋 📦 Cache Optimizations 🚩
  140. CI Speed Optimizations Cache Dependencies Cache Build Tasks Cache Configuration

    JVM Tuning Compiler Flags 📦 treeless or shallow clone prune images 🏋 📦 Cache Optimizations 🚩 🚧 ✅ ✅ ✅ ✅ Future Talk
  141. Gradle Documentation Gradle Files & Directories https://docs.gradle.org/current/userguide/directory_layout.html Gradle CI Ephemeral

    Best Practices https://docs.gradle.org/current/userguide/ dependency_resolution.html#sub:ephemeral-ci-cache Gradle Recommends using Daemons for CI https://docs.gradle.org/current/userguide/ gradle_daemon.html#continuous_integration
  142. Kotlin JVM Args Documentation New Method https://kotlinlang.org/docs/gradle-compilation-and-caches.html#kotlin- daemon-jvmargs-property Deprecated Method

    https://kotlinlang.org/docs/gradle-compilation-and-caches.html#kotlin- daemon-jvm-options-system-property
  143. JDK Version Documentation JDK Release Notes Summaries for GC Algo

    Changes 19 | 20 | 21 JVM SoftReferences Tuning CodeCache