Last month, one of my pull requests was merged into the popular community project, React Native Camera, in response to a tweet from one of the maintainers. React Native Camera’s builds were blocked because they were trying to use a resource class not available on the free/OSS plans. On top of unblocking them, I made some additional optimizations.
The tactics I used in this PR can be generalized and used to optimize many other projects. In this post, I’ll break down some of the changes I made to the React Native Camera project, explain how they improved performance, and suggest ways that you can use similar techniques to improve your own projects.
Caching step placement
Generally, people have a habit of placing their save_cache
statements at the end of their build. React Native Camera was doing the same, like so:
jobs:
build-app:
# ...environment setup here
steps:
- checkout
- restore_cache:
keys:
- v1-npm-{{ .Branch }}-{{ checksum "yarn.lock" }}
- v1-npm
- run:
name: Install Dependencies
command: yarn install --ignore-engines
# ...other steps, etc.
- save_cache:
key: v1-npm
paths:
- node_modules/
- save_cache:
key: v1-npm-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- node_modules/
# ...rest of config
This is not optimal because saving the cache will not happen if any of the other steps in the job fail before the save_cache
step has a chance to execute. It’s best to place the save_cache
step immediately after a dependency step - this guarantees the cache will be saved as long as the dependency step works:
jobs:
build-app:
# ...environment setup
steps:
- checkout
- restore_cache:
keys:
- v1-npm-{{ .Branch }}-{{ checksum "yarn.lock" }}
- v1-npm
- run:
name: Install Dependencies
command: yarn install --ignore-engines
#### MOVED
- save_cache:
key: v1-npm
paths:
- node_modules/
- save_cache:
key: v1-npm-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- node_modules/
#### /MOVED
# ...rest of config
You can find out more information about caching and see examples in our docs.
Adding caching for Gradle
Another change I made was adding caching for Gradle dependencies. Their Run Checks
and Build Sample App
steps in CircleCI were Gradle Wrapper executions, and they were taking up the majority of the build times. In the config below, I store the Gradle cache as well as the wrapper.
jobs:
build-app:
# ...environment setup
steps:
# ...previous build steps
#### ADDED
- restore_cache:
keys:
- v1-gradle-{{ checksum "android/gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "examples/basic/android/gradle/wrapper/gradle-wrapper.properties" }}
- v1-gradle-wrapper
- restore_cache:
keys:
- v1-gradle-cache-{{ checksum "android/build.gradle" }}-{{ checksum "examples/basic/android/build.gradle" }}
- v1-gradle-cache
#### /ADDED
- run:
name: Run Checks
command: |
cd android
chmod +x ./gradlew && ./gradlew check
# ...other step(s)
- run:
name: Run Yarn to Generate react.gradle
command: cd examples/basic/android && yarn
- run:
name: Build Sample App
command: |
cd examples/basic/android && chmod +x ./gradlew && ./gradlew build
#### ADDED
- save_cache:
key: v1-gradle-wrapper-{{ checksum "examples/basic/android/gradle/wrapper/gradle-wrapper.properties" }}
paths:
- ~/.gradle/wrapper
- save_cache:
key: v1-gradle-cache-{{ checksum "examples/basic/android/build.gradle" }}
paths:
- ~/.gradle/caches
#### /ADDED
# ...rest of config
Making artifacts from reports
Did you know that our artifacts act like a static file server? This is useful for storing things like HTML reports for your test suites and accessing them like web pages. I added an artifact step to store the generated output of the linting commands. This allows you to browse artifacts on the artifacts tab, and access those files like they are webpages.
jobs:
build-app:
# ...environment setup
steps:
# ...previous build steps
- run:
name: Run Checks
command: |
cd android
chmod +x ./gradlew && ./gradlew check
#### ADDED
- store_artifacts:
path: android/build/reports
#### /ADDED
# ...other step(s)
- run:
name: Build Sample App
command: |
cd examples/basic/android && chmod +x ./gradlew && ./gradlew build
#### ADDED
- store_artifacts:
path: examples/basic/android/app/build/reports
destination: app
#### /ADDED
# ...rest of config
You can find more information about creating artifacts in our docs.
Conclusion
The small changes made in this pull request are just a few examples of features you could make use of to optimize your CircleCI builds. Documentation for our platform is extensive, with various examples and tutorials. The CircleCI blog is also a great resource. And if you get stuck, try tweeting to us @CircleCI!
For more optimization features and techniques read our Optimizing open source project builds on CircleCI post.
Thanks for reading, and happy building!