Skip to content

Commit

Permalink
Detect ccache and provide a default configuration (#42051)
Browse files Browse the repository at this point in the history
Summary:
Building native modules from source, may take a long time. Xcode already helps bring this down, by providing incremental builds, as long as the user doesn't delete their `ios/build` directory. But in some situations, i.e. when iterating the native code of an app or library or when the developer need to delete that `ios/build` directory, it's advantageous to use a compiler cache, such as ccache. This is already outlined in our ["Speeding up your Build phase"](https://reactnative.dev/docs/build-speed#xcode-specific-setup) guide.

But setting up an Xcode project to use Ccache with the correct configuration, isn't trivial in a way that doesn't require symlinking `clang` and `clang++` or passing configuration via environment variables on every `npm run ios` invokation.

This PR takes its inspiration from the existing guide on [setting up Ccache for Xcode](https://reactnative.dev/docs/build-speed#xcode-specific-setup), but applies the build settings only if an installation of `ccache` is detected and the feature is explicitly opted into via an argument to the `react_native_post_install` function or a `USE_CCACHE` environment variable. It uses two shell scripts to wrap the call to `ccache`, which both injects a default `CCACHE_CONFIGPATH` environment variable (i.e. it won't override this if already provided, to allow for customisations on CI), pointing to a `ccache.config` which works well with React Native projects (it has the same values as the guide mentions).

For context, I posted about this change in the ios channel of the contributors Discord server, where I discussed it with cipolleschi and saadnajmi

### Additional output printed when running `pod install`

#### When `ccache_available and ccache_enabled`

```
[Ccache]: Ccache found at /opt/homebrew/bin/ccache
[Ccache]: Setting CC, LD, CXX & LDPLUSPLUS build settings
```

#### When `ccache_available and !ccache_enabled`

```
[Ccache]: Ccache found at /opt/homebrew/bin/ccache
[Ccache]: Pass ':ccache_enabled => true' to 'react_native_post_install' in your Podfile or set environment variable 'USE_CCACHE=1' to increase the speed of subsequent builds
```

#### When `!ccache_available and ccache_enabled`

```
[!] [Ccache]: Install ccache or ensure your neither passing ':ccache_enabled => true' nor setting environment variable 'USE_CCACHE=1'
```

#### Otherwise

If the user doesn't have ccache installed and doesn't explicitly opt into this feature, nothing will be printed.

bypass-github-export-checks

## Changelog:

[IOS] [ADDED] - Added better support for `ccache`, to speed up subsequent builds of native code. After installing `ccache` and running `pod install`, the Xcode project is injected with compiler and linker build settings pointing scripts that loads a default  Ccache configuration and invokes the `ccache` executable.

Pull Request resolved: #42051

Test Plan:
I've tested this manually - would love some inspiration on how to automate this, if the reviewer deem it needed.
To test this locally:
1. Install Ccache and make sure the `ccache` executable is in your `PATH` (verify by running `ccache --version`)
2. Create a new template app instance and apply the changes of this PR to the `node_modules/react-native` package.
3. Set the `USE_CCACHE` environment variable using `export USE_CCACHE=1`.
4. Run `pod install` in the `ios` directory.
5. Check the stats of Ccache (running `ccache -s`).
6. Run `npm run ios` or build the project from Xcode.
7. Check the Ccache stats again to verify ccache is intercepting compilation ("Cacheable calls" should ideally be 100%).
8. To check the speed gain:
  a. Delete the `ios/builds` directory
  b. Zero out the ccache stats (by running `ccache -z`)
  c. Run `pod install` again (only needed if you ran the initial `pod install` with new architecture enabled `RCT_NEW_ARCH_ENABLED=1`).
  d. Run `npm run ios` or build the project from Xcode.
  e. This last step should be significantly faster and you should see "Hits" under "Local storage" in the ccache stats approach 100%.

Reviewed By: huntie

Differential Revision: D52431507

Pulled By: cipolleschi

fbshipit-source-id: 6cfe39acd6250fae03959f0ee74d1f2fc46b0827
  • Loading branch information
kraenhansen authored and facebook-github-bot committed Dec 28, 2023
1 parent e25a9b4 commit e85d51c
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 2 deletions.
36 changes: 36 additions & 0 deletions packages/react-native/scripts/cocoapods/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,42 @@ def self.set_node_modules_user_settings(installer, react_native_path)
end
end

def self.set_ccache_compiler_and_linker_build_settings(installer, react_native_path, ccache_enabled)
projects = self.extract_projects(installer)

ccache_path = `command -v ccache`.strip
ccache_available = !ccache_path.empty?

message_prefix = "[Ccache]"

if ccache_available
Pod::UI.puts("#{message_prefix}: Ccache found at #{ccache_path}")
end

if ccache_available and ccache_enabled
Pod::UI.puts("#{message_prefix}: Setting CC, LD, CXX & LDPLUSPLUS build settings")
# Using scripts wrapping the ccache executable, to allow injection of configurations
ccache_clang_sh = File.join("$(REACT_NATIVE_PATH)", 'scripts', 'xcode', 'ccache-clang.sh')
ccache_clangpp_sh = File.join("$(REACT_NATIVE_PATH)", 'scripts', 'xcode', 'ccache-clang++.sh')

projects.each do |project|
project.build_configurations.each do |config|
# Using the un-qualified names means you can swap in different implementations, for example ccache
config.build_settings["CC"] = ccache_clang_sh
config.build_settings["LD"] = ccache_clang_sh
config.build_settings["CXX"] = ccache_clangpp_sh
config.build_settings["LDPLUSPLUS"] = ccache_clangpp_sh
end

project.save()
end
elsif ccache_available and !ccache_enabled
Pod::UI.puts("#{message_prefix}: Pass ':ccache_enabled => true' to 'react_native_post_install' in your Podfile or set environment variable 'USE_CCACHE=1' to increase the speed of subsequent builds")
elsif !ccache_available and ccache_enabled
Pod::UI.warn("#{message_prefix}: Install ccache or ensure your neither passing ':ccache_enabled => true' nor setting environment variable 'USE_CCACHE=1'")
end
end

def self.fix_library_search_paths(installer)
projects = self.extract_projects(installer)

Expand Down
4 changes: 3 additions & 1 deletion packages/react-native/scripts/react_native_pods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ def get_default_flags()
def react_native_post_install(
installer,
react_native_path = "../node_modules/react-native",
mac_catalyst_enabled: false
mac_catalyst_enabled: false,
ccache_enabled: ENV['USE_CCACHE'] == '1'
)
ReactNativePodsUtils.turn_off_resource_bundle_react_core(installer)

Expand All @@ -276,6 +277,7 @@ def react_native_post_install(
ReactNativePodsUtils.update_search_paths(installer)
ReactNativePodsUtils.set_use_hermes_build_setting(installer, hermes_enabled)
ReactNativePodsUtils.set_node_modules_user_settings(installer, react_native_path)
ReactNativePodsUtils.set_ccache_compiler_and_linker_build_settings(installer, react_native_path, ccache_enabled)
ReactNativePodsUtils.apply_xcode_15_patch(installer)
ReactNativePodsUtils.apply_ats_config(installer)
ReactNativePodsUtils.updateOSDeploymentTarget(installer)
Expand Down
14 changes: 14 additions & 0 deletions packages/react-native/scripts/xcode/ccache-clang++.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# Get the absolute path of this script
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

REACT_NATIVE_CCACHE_CONFIGPATH=$SCRIPT_DIR/ccache.conf
# Provide our config file if none is already provided
export CCACHE_CONFIGPATH="${CCACHE_CONFIGPATH:-$REACT_NATIVE_CCACHE_CONFIGPATH}"

exec ccache clang++ "$@"
14 changes: 14 additions & 0 deletions packages/react-native/scripts/xcode/ccache-clang.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# Get the absolute path of this script
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

REACT_NATIVE_CCACHE_CONFIGPATH=$SCRIPT_DIR/ccache.conf
# Provide our config file if none is already provided
export CCACHE_CONFIGPATH="${CCACHE_CONFIGPATH:-$REACT_NATIVE_CCACHE_CONFIGPATH}"

exec ccache clang "$@"
11 changes: 11 additions & 0 deletions packages/react-native/scripts/xcode/ccache.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# See https://ccache.dev/manual/4.3.html#_configuration_options for details and available options

sloppiness = clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
file_clone = true
depend_mode = true
inode_cache = true
3 changes: 2 additions & 1 deletion packages/react-native/template/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ target 'HelloWorld' do
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)
end
end

0 comments on commit e85d51c

Please sign in to comment.