- Bazel aspects
- Bazel configs
- Command-line API
The xcodeproj_cache_warm_aspect aspect is useful for skipping non-compilation
related actions. For example, in CI it can be used to help with disk space
usage by skipping actions that are typically not cached during a cache warming
job. It can also be used to validate that the targets compile while skipping
costly actions like bundling, signing, etc.
To use the aspect, you apply it at the command line:
bazel build //some:target \
--aspects=@rules_xcodeproj//xcodeproj:xcodeproj_cache_warm_aspect.bzl%xcodeproj_cache_warm_aspect \
--output_groups=compiles
You can also create a Bazel configuration in a .bazelrc file to reuse the
aspect easily:
common:cache_warming --aspects=@rules_xcodeproj//xcodeproj:xcodeproj_cache_warm_aspect.bzl%xcodeproj_cache_warm_aspect
common:cache_warming --output_groups=compiles
And use it, for example, with the command-line API:
bazel run //label/to:xcodeproj \
-- \
'build --config=cache_warming --remote_download_minimal $_GENERATOR_LABEL_'
If you want to also cache resource processing (e.g. asset catalog compiles,
Info.plist processing, entitlement processing, etc.), then you can also
include the resources output group:
common:cache_warming --output_groups=compiles,resources
The way your project is generated, and the way Bazel builds it inside of Xcode,
can be customized with configs in .bazelrc files.
The rules_xcodeproj config is used when building the project inside of Xcode.
It’s also inherited by all of the other rules_xcodeproj_* configs.
Warning
Build affecting flags need to be the same between all rules_xcodeproj{_*}
configs, so it’s usually better to adjust this config (rules_xcodeproj) over
the other ones, unless you actually need to target them specifically.
The rules_xcodeproj_generator config is used when generating the project (i.e.
when you run bazel run //:xcodeproj).
The types of things you might want to adjust on this config are non-build affecting, like adjusting build log output.
The rules_xcodeproj_indexbuild config is used when Xcode performs an Index
Build (also known as “Prepare for Indexing”), where it builds the project in the
background to a separate output directory, to ensure that it has the required
artifacts to perform per-file indexing based compiles.
Since Index Build runs in the background quite frequently, sometimes once per
target in the project, and then once per saving of a file, the types of things
you might want to adjust on this config are probably log output or telemetry
related (and the default config disables BES upload for this reason). For
example, if you set --profile globally or on the rules_xcodeproj config,
you will want to set --profile= (clearing the value) on
rules_xcodeproj_indexbuild to prevent Index Builds from overwriting the
profile:
build:rules_xcodeproj --profile=/tmp/profile.gz
build:rules_xcodeproj_indexbuild --profile=
The rules_xcodeproj_swiftuipreviews config is used when Xcode performs a
SwiftUI Preview build.
You shouldn’t need to adjust this config. The default config applies the needed build adjusting flags.
Note
Code coverage in Xcode with Bazel requires apple_support 2.0.0 or later and rules_swift 3.4.1 or later.
The rules_xcodeproj_coverage config is used when building the project tests
inside of Xcode when code coverage is enabled. This config sets the needed
features inside apple_support and rules_swift to generate coverage data with
absolute paths to sources so Xcode can map them correctly.
By default, code coverage is disabled for test actions of schemes. You can
enable code coverage in Xcode by setting code_coverage = True on an
xcschemes.test_options
declaration, or by enabling the "Gather coverage data" option in the scheme editor.
Warning
Enabling code coverage will cause tests to be built for coverage, which is a
distinct configuration mode from normal test builds. For building in Xcode
in particular, this results in build outputs that are inherently non-hermetic
as they contain absolute paths to source files. This will result in poor
cache hit rates for these builds. This does not impact coverage builds run
using the command-line API or normal bazel test runs.
It is recommended to only enable code coverage inside of Xcode when actively
using it for development, and to use tool sets like
lcov to produce coverage reports
from normal bazel test runs.
Each xcodeproj can specify a set of configs which inherit from the
rules_xcodeproj family of configs. You can specify the base config with the
config attribute. For example, if you set config = "projectx_xcodeproj",
then the projectx_xcodeproj, projectx_xcodeproj_generator,
projectx_xcodeproj_indexbuild, and projectx_xcodeproj_swiftuipreviews
configs are available to adjust, and they all inherit from their respective
rules_xcodeproj{_*} configs.
Using this feature adds a layer of indirection, and should only be used if you have project-specific configurations you need to apply.
Finally, there is one last way to adjust the Bazel configs, through the use of
the --@rules_xcodeproj//xcodeproj:extra_*_flags
family of build flags. These flags apply changes to the configs after all other
sources, and work when calling bazel run to generate the project. If using
project-level configs, these flags adjust those instead of the base configs.
extra_common_flags: Applied to the parent config (i.e.rules_xcodeproj)extra_generator_flags: Applied to the generator config (i.e.rules_xcodeproj_generator)extra_indexbuild_flags: Applied to the Index Build config (i.e.rules_xcodeproj_indexbuild)extra_swiftuipreviews_flags: Applied to the Xcode Previews build config (i.e.rules_xcodeproj_swiftuipreviews)
A project xcodeproj.bazelrc file is loaded before the workspace .bazelrc.
It’s created from a
template, which contains
the default configs mentioned above, and will also contain stubs for the
project-level configs if they are used.
At the end of the project xcodeproj.bazelrc file is a conditional import of a
workspace level xcodeproj.bazelrc file. Since startup flags (e.g.
--host_jvm_args) can’t be applied to configs, they can instead be set in this
file, and they will only apply to rules_xcodeproj bazel invocations. If you
have to generate all or part of your rules_xcodeproj configs, this is a
convenient file to use for that.
Note
--output_baseis set by rules_xcodeproj in order to nest the output base inside of the primary output base (see the command-line API section for more details). Thus, settingstartup --output_baseinxcodeproj.bazelrcwill have no effect.
Next the normal workspace .bazelrc file is imported. Here you can adjust the
configs further.
Finally, if any
--@rules_xcodeproj//xcodeproj:extra_*_flags build
flags were used during project generation, then those adjustments are made in
a project xcodeproj_extra_flags.bazelrc file, which is loaded after the
workspace .bazelrc file. This ensures that they override any flags set
earlier, mimicking the behavior of command-line set flags taking precedence.
By default, rules_xcodeproj configures Xcode to use a combined output base For
both normal builds and index builds. This is done to save disk space and improve
cache hit rates, since both types of builds can share outputs. However, if you
find that index builds are interfering with normal builds, you can disable
this behavior during project generation by setting
--@rules_xcodeproj//xcodeproj:separate_index_build_output_base in your bazelrc.
rules_xcodeproj builds targets in its own
output base.
It does this to ensure that the
analysis cache
isn’t affected by other Bazel commands, including project generation itself.
In addition, rules_xcodeproj sets one of the project’s
Bazel configs. Because of this, normal Bazel commands, such
as bazel build or bazel clean, won’t be the same as what is performed in
Xcode.
To enable you to perform Bazel commands in the same “environment” that
rules_xcodeproj itself uses, we provide a command-line API. Assuming your
xcodeproj target is defined at //:xcodeproj, this is how you can call this
API:
bazel run //:xcodeproj -- [option ...] command_string
For example, this will call bazel info output_path in the rules_xcodeproj
environment:
bazel run //:xcodeproj -- 'info output_path'
This will build all targets in the project the same way as Xcode Previews does:
bazel run //:xcodeproj -- --config=swiftuipreviews --generator_output_groups=all_targets build
The API supports all the
commands
bazel supports. It does not support providing
startup options,
though you can specify those in the
xcodeproj.bazelrc file.
Below are notes about various commands.
To build targets the same way as rules_xcodeproj requires more than just using
this API, because the xcodeproj rule applies a
configuration transition
to targets. That means that building targets by specifying their labels will
build potentially different versions of those targets, and minimally versions
that have different cache keys.
rules_xcodeproj uses output groups to “address” these correctly configured targets. It uses a set of private output groups, but it also exposes some public ones.
To build these output groups with this API you would have to craft a call like this (note: this is not the recommended way to do this, and might break in the future, continue reading after the example for the recommended way):
bazel run //:xcodeproj -- 'build --remote_download_minimal --output_groups=all_targets //:xcodeproj.generator'
This requires knowing the internal name of the generator target
(//:xcodeproj.generator in this example), and it also doesn’t apply some flags
that Xcode bazel build command applies (e.g.
--remote_download_regex). Instead, it’s recommended that you use
the --generator_output_groups option:
bazel run //:xcodeproj -- --generator_output_groups=all_targets 'build --remote_download_minimal'
When you run bazel clean normally (i.e. not using this API), it won’t affect
the rules_xcodeproj output base the way you expect. bazel clean --expunge
will though, as it will blow away both environments. To clean the
rules_xcodeproj output base use the API instead:
bazel run //:xcodeproj -- clean
Depending on how you have your rules_xcodeproj Bazel configs
set up, you might be able to run bazel query without using the API. I
recommend using the API instead though, to prevent fetching external
dependencies in the primary output base. The other queries, cquery and
aquery, should always be performed through the API, to ensure the targets
are properly configured:
bazel run //:xcodeproj -- 'aquery "set(//some:target)"'
You can specify some options before the Bazel command:
Prints the command that was executed.
Without -v:
$ bazel run --config=cache //:xcodeproj -- clean
INFO: Invocation ID: e4be5bb9-1823-4ca9-a3fd-6066f936460a
INFO: Analyzed target //:xcodeproj (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:xcodeproj up-to-date:
/Users/brentley/Developer/rules_xcodeproj/bazel-output-base/execroot/rules_xcodeproj/bazel-out/darwin_arm64-fastbuild/bin/xcodeproj-runner.sh
INFO: Elapsed time: 0.285s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Running command line: /Users/brentley/Developer/rules_xcodeproj/bazel-output-base/execroot/rules_xcodeproj/bazel-out/darwin_arm64-fastbuild/bin/xcodeproj-runner.sh -v clean
INFO: Build completed successfully, 1 total action
INFO: Invocation ID: 84c53471-73a4-4267-9289-0ad076ee94fb
INFO: Starting clean.
With -v:
$ bazel run --config=cache //:xcodeproj -- -v clean
INFO: Invocation ID: e4be5bb9-1823-4ca9-a3fd-6066f936460a
INFO: Analyzed target //:xcodeproj (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:xcodeproj up-to-date:
/Users/brentley/Developer/rules_xcodeproj/bazel-output-base/execroot/rules_xcodeproj/bazel-out/darwin_arm64-fastbuild/bin/xcodeproj-runner.sh
INFO: Elapsed time: 0.285s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Running command line: /Users/brentley/Developer/rules_xcodeproj/bazel-output-base/execroot/rules_xcodeproj/bazel-out/darwin_arm64-fastbuild/bin/xcodeproj-runner.sh -v clean
INFO: Build completed successfully, 1 total action
Running Bazel command:
+ env PATH=/bin:/usr/bin /Users/brentley/Library/Caches/bazelisk/downloads/bazelbuild/bazel-5.3.0-darwin-arm64/bin/bazel --host_jvm_args=-Xdock:name=/Applications/Xcode-14.0.1.app/Contents/Developer --noworkspace_rc --bazelrc=/Users/brentley/Developer/rules_xcodeproj/bazel-output-base/execroot/rules_xcodeproj/bazel-out/darwin_arm64-fastbuild/bin/xcodeproj-runner.sh.runfiles/rules_xcodeproj/xcodeproj.bazelrc --bazelrc=.bazelrc --bazelrc=/Users/brentley/Developer/rules_xcodeproj/bazel-output-base/execroot/rules_xcodeproj/bazel-out/darwin_arm64-fastbuild/bin/xcodeproj-runner.sh.runfiles/rules_xcodeproj/xcodeproj-extra-flags.bazelrc --output_base /Users/brentley/Developer/rules_xcodeproj/bazel-output-base/execroot/_rules_xcodeproj/build_output_base clean --repo_env=DEVELOPER_DIR=/Applications/Xcode-14.0.1.app/Contents/Developer --repo_env=USE_CLANG_CL=14A400 --config=_rules_xcodeproj_build
INFO: Invocation ID: 84c53471-73a4-4267-9289-0ad076ee94fb
INFO: Starting clean.
Changes the Bazel config that is used. Valid values are:
build:rules_xcodeprojor the project-level equivalent. This is the default if--configisn’t specified.indexbuild:rules_xcodeproj_indexbuildor the project-level equivalent.swiftuipreviews:rules_xcodeproj_swiftuipreviewsor the project-level equivalent.
For example, this will build all targets in the project the same way as Xcode Previews does:
bazel run //:xcodeproj -- --config=swiftuipreviews --generator_output_groups=all_targets build
If the Bazel command is build, then this builds the specified generator
outputs groups, potentially adding additional flags to match the behavior of
Xcode’s bazel build (e.g. --remote_download_regex).
These are the available output groups to use:
all_targets: This will build every target specified bytop_level_targets. This is useful to build in “cache warming” jobs.
For example, this will build all targets the same way that Xcode does:
bazel run //:xcodeproj -- --generator_output_groups=all_targets build
By default, when using
[--generator_output_groups][#--generator_output_groups],
intermediate outputs (such as generated files and index stores) won't be
downloaded from a remote cache if --remote_download_toplevel or
--remote_download_minimal is used. This is to support the common use case of
using the command-line API to warm a remote cache on a CI job that uses
--remote_download_minimal and doesn't want anything downloaded.
To cause intermediates to be downloaded the same way that a normal Xcode build
downloads them, pass the --download_intermediates flag:
bazel run //:xcodeproj -- --generator_output_groups=all_targets --download_intermediates build
In your command, any reference to these variables will be expanded:
-
$_GENERATOR_LABEL_: The label of the generator target (e.g.@@_main~internal~rules_xcodeproj_generated//generator/xcodeproj). Useful for certainaquerycommands:$bazel run //:xcodeproj -- 'aquery $_GENERATOR_LABEL_' ... INFO: Invocation ID: ca31d4b4-0df5-49de-a020-c70a922521af INFO: Streaming build results to: https://app.buildbuddy.io/invocation/ca31d4b4-0df5-49de-a020-c70a922521af INFO: Analyzed target @_main~internal~rules_xcodeproj_generated//generator/tools/generators/legacy/xcodeproj:xcodeproj (1 packages loaded, 2 targets configured). INFO: Found 1 target... action 'Writing file external/_main~internal~rules_xcodeproj_generated/generator/xcodeproj/xcodeproj-xcode_generated_paths.json' Mnemonic: FileWrite Target: @rules_xcodeproj_generated//generator/xcodeproj:xcodeproj Configuration: darwin_arm64-dbg Execution platform: @local_config_platform//:host ActionKey: f0332c573c0b3ae2ebe525959057fd901d6ce582948b19f7b8e2fdee3f43f045 Inputs: [] Outputs: [bazel-out/darwin_arm64-dbg/bin/external/_main~internal~rules_xcodeproj_generated/generator/xcodeproj/xcodeproj-xcode_generated_paths.json] ...