From 4d79a7226a85694452b2eca5a5fdf042e1d53c3b Mon Sep 17 00:00:00 2001 From: Brentley Jones Date: Mon, 11 May 2026 10:38:27 -0500 Subject: [PATCH 1/3] Add `generator_bazel_env` Allows setting environment variables that affect only the nested generator call, but not the Xcode based calls. An example use case is setting `tools/bazel` output affecting env variables (e.g. color or verbosity). Signed-off-by: Brentley Jones --- xcodeproj/internal/xcodeproj_runner.bzl | 22 ++++++++++++++++++++++ xcodeproj/xcodeproj.bzl | 16 ++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/xcodeproj/internal/xcodeproj_runner.bzl b/xcodeproj/internal/xcodeproj_runner.bzl index 9454b5490..1d456727f 100644 --- a/xcodeproj/internal/xcodeproj_runner.bzl +++ b/xcodeproj/internal/xcodeproj_runner.bzl @@ -272,6 +272,7 @@ def _write_runner( execution_root_file, extra_flags_bazelrc, extra_generator_flags, + generator_bazel_env, generator_build_file, generator_defs_bzl, install_path, @@ -323,6 +324,25 @@ fi ), )) + for key, value in generator_bazel_env.items(): + if value == "\0": + collect_statements.append("""\ +if [[ -n "${{{key}:-}}" ]]; then + envs+=("{key}=${key}") +fi +""".format(key = key)) + else: + base_envs_values.append(" \"{}={}\"".format( + key, + ( + value.replace( + # Escape double quotes for bash + "\"", + "\\\"", + ) + ), + )) + collect_bazel_env = """\ envs=( {base_envs_values} @@ -439,6 +459,7 @@ def _xcodeproj_runner_impl(ctx): extra_generator_flags = ( ctx.attr._extra_generator_flags[BuildSettingInfo].value ), + generator_bazel_env = ctx.attr.generator_bazel_env, generator_build_file = generator_build_file, generator_defs_bzl = generator_defs_bzl, install_path = install_path, @@ -474,6 +495,7 @@ xcodeproj_runner = rule( "config": attr.string(mandatory = True), "default_xcode_configuration": attr.string(), "focused_labels": attr.string_list(default = []), + "generator_bazel_env": attr.string_dict(mandatory = True), "generation_shard_count": attr.int(mandatory = True), "import_index_build_indexstores": attr.bool(mandatory = True), "install_directory": attr.string(mandatory = True), diff --git a/xcodeproj/xcodeproj.bzl b/xcodeproj/xcodeproj.bzl index 76f6a13a5..021d025f4 100644 --- a/xcodeproj/xcodeproj.bzl +++ b/xcodeproj/xcodeproj.bzl @@ -31,6 +31,7 @@ def xcodeproj( default_xcode_configuration = None, extra_files = [], focused_targets = [], + generator_bazel_env = {}, import_index_build_indexstores = True, install_directory = None, ios_device_cpus = "arm64", @@ -150,6 +151,13 @@ def xcodeproj( listed explicitly in the `unfocused_targets` argument. The labels must match transitive dependencies of the targets specified in the `top_level_targets` argument. + generator_bazel_env: Optional. A `dict` of environment variables to + set when invoking `bazel_path` during project generation only. + + This behaves the same as `bazel_env`, but only applies to the + generated runner's Bazel invocations while generating the project. + These values are not written into the generated `.xcodeproj`, and + they do not apply to Bazel builds launched later from Xcode. import_index_build_indexstores: Optional. Whether to import the index stores generated by Index Build. @@ -365,6 +373,7 @@ def xcodeproj( if not bazel_path: bazel_path = "bazel" bazel_env = dict(bazel_env) if bazel_env else {} + generator_bazel_env = dict(generator_bazel_env) if generator_bazel_env else {} if "PATH" not in bazel_env: bazel_env["PATH"] = "/bin:/usr/bin" if "LANG" not in bazel_env: @@ -391,6 +400,12 @@ def xcodeproj( key: "\0" if value == None else value for key, value in sorted(bazel_env.items()) } + generator_bazel_env = { + # Null character is used to represent `None`, since `attr.string_dict` + # requires non-`None` values. + key: "\0" if value == None else value + for key, value in sorted(generator_bazel_env.items()) + } if default_xcode_configuration and default_xcode_configuration not in xcode_configurations: keys = sorted(xcode_configurations.keys()) @@ -530,6 +545,7 @@ for {configuration} ({new_keys}) do not match keys of other configurations \ config = config, default_xcode_configuration = default_xcode_configuration, focused_labels = focused_labels, + generator_bazel_env = generator_bazel_env, generation_shard_count = generation_shard_count, import_index_build_indexstores = import_index_build_indexstores, install_directory = install_directory, From 6c6cc9cae4d26009f8e99699bd81a1ff01ad0987 Mon Sep 17 00:00:00 2001 From: Brentley Jones Date: Mon, 11 May 2026 11:10:55 -0500 Subject: [PATCH 2/3] Docs and lint Signed-off-by: Brentley Jones --- docs/bazel.md | 14 ++++++++------ xcodeproj/internal/xcodeproj_runner.bzl | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/bazel.md b/docs/bazel.md index 179dc0051..fcdfa4e88 100755 --- a/docs/bazel.md +++ b/docs/bazel.md @@ -53,12 +53,13 @@ load("@rules_xcodeproj//xcodeproj:xcodeproj.bzl", "xcodeproj") load("@rules_xcodeproj//xcodeproj/internal/docs:xcodeproj.bzl", "xcodeproj") xcodeproj(*, name, associated_extra_files, bazel_path, bazel_env, config, - default_xcode_configuration, extra_files, focused_targets, import_index_build_indexstores, - install_directory, ios_device_cpus, ios_simulator_cpus, minimum_xcode_version, post_build, - pre_build, project_name, project_options, scheme_autogeneration_mode, - scheme_autogeneration_config, target_name_mode, top_level_targets, tvos_device_cpus, - tvos_simulator_cpus, unfocused_targets, visionos_device_cpus, visionos_simulator_cpus, - watchos_device_cpus, watchos_simulator_cpus, xcode_configurations, xcschemes, **kwargs) + default_xcode_configuration, extra_files, focused_targets, generator_bazel_env, + import_index_build_indexstores, install_directory, ios_device_cpus, ios_simulator_cpus, + minimum_xcode_version, post_build, pre_build, project_name, project_options, + scheme_autogeneration_mode, scheme_autogeneration_config, target_name_mode, + top_level_targets, tvos_device_cpus, tvos_simulator_cpus, unfocused_targets, + visionos_device_cpus, visionos_simulator_cpus, watchos_device_cpus, watchos_simulator_cpus, + xcode_configurations, xcschemes, **kwargs) Creates an `.xcodeproj` file in the workspace when run. @@ -97,6 +98,7 @@ xcodeproj( | default_xcode_configuration | Optional. The name of the the Xcode configuration to use when building, if not overridden by custom schemes.

If not set, the first Xcode configuration alphabetically will be used. Use [`xcode_configurations`](#xcodeproj-xcode_configurations) to adjust Xcode configurations. | `None` | | extra_files | Optional. A `list` of extra `File`s to be added to the project. | `[]` | | focused_targets | Optional. A `list` of target labels as `string` values.

If specified, only these targets will be included in the generated project; all other targets will be excluded, as if they were listed explicitly in the `unfocused_targets` argument. The labels must match transitive dependencies of the targets specified in the `top_level_targets` argument. | `[]` | +| generator_bazel_env | Optional. A `dict` of environment variables to set when invoking `bazel_path` during project generation only.

This behaves the same as `bazel_env`, but only applies to the generated runner's Bazel invocations while generating the project. These values are not written into the generated `.xcodeproj`, and they do not apply to Bazel builds launched later from Xcode. | `{}` | | import_index_build_indexstores | Optional. Whether to import the index stores generated by Index Build.

This is useful if you want to use the index stores generated by Index Build to speed up Xcode's indexing process. You may not want this enabled if the additional work (mainly disk IO) of importing the index stores is not worth it for your project. | `True` | | install_directory | Optional. The directory where the generated project will be written to.

The path is relative to the workspace root.

Defaults to the directory that the `xcodeproj` target is declared in (e.g. if the `xcodeproj` target is declared in `//foo/bar:BUILD` then the default value is `"foo/bar"`). Use `""` to have the project generated in the workspace root. | `None` | | ios_device_cpus | Optional. The value to use for `--ios_multi_cpus` when building the transitive dependencies of the targets specified in the `top_level_targets` argument with the `"device"` `target_environment`.

**Warning:** Changing this value will affect the Starlark transition hash of all transitive dependencies of the targets specified in the `top_level_targets` argument with the `"device"` `target_environment`, even if they aren't iOS targets. | `"arm64"` | diff --git a/xcodeproj/internal/xcodeproj_runner.bzl b/xcodeproj/internal/xcodeproj_runner.bzl index 1d456727f..1cf22a44a 100644 --- a/xcodeproj/internal/xcodeproj_runner.bzl +++ b/xcodeproj/internal/xcodeproj_runner.bzl @@ -495,8 +495,8 @@ xcodeproj_runner = rule( "config": attr.string(mandatory = True), "default_xcode_configuration": attr.string(), "focused_labels": attr.string_list(default = []), - "generator_bazel_env": attr.string_dict(mandatory = True), "generation_shard_count": attr.int(mandatory = True), + "generator_bazel_env": attr.string_dict(mandatory = True), "import_index_build_indexstores": attr.bool(mandatory = True), "install_directory": attr.string(mandatory = True), "ios_device_cpus": attr.string(mandatory = True), From 151c796946104e087029049008e9eb6eee45dfe5 Mon Sep 17 00:00:00 2001 From: Brentley Jones Date: Mon, 11 May 2026 12:20:57 -0500 Subject: [PATCH 3/3] Use constants Signed-off-by: Brentley Jones --- xcodeproj/internal/xcodeproj_runner.bzl | 13 ++++++++++--- xcodeproj/xcodeproj.bzl | 12 ++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/xcodeproj/internal/xcodeproj_runner.bzl b/xcodeproj/internal/xcodeproj_runner.bzl index 1cf22a44a..f94889627 100644 --- a/xcodeproj/internal/xcodeproj_runner.bzl +++ b/xcodeproj/internal/xcodeproj_runner.bzl @@ -6,6 +6,11 @@ load(":collections.bzl", "uniq") load(":execution_root.bzl", "write_execution_root_file") load(":providers.bzl", "XcodeProjRunnerOutputInfo") +# Null character is used to represent `None`, since `attr.string_dict` +# requires non-`None` values. +_NULL_BAZEL_ENV_VALUE = "\0" +_NULL_BAZEL_ENV_VALUE_LITERAL = "\\0" + def _process_extra_flags(*, attr, content, setting, config, config_suffix): extra_flags = getattr(attr, setting)[BuildSettingInfo].value if extra_flags: @@ -287,8 +292,10 @@ def _write_runner( base_envs_values = [] collect_statements = [] for key, value in bazel_env.items(): - if value == "\0": - base_def_env_values.append(' \\"{}\\": \\"\\\\0\\",'.format(key)) + if value == _NULL_BAZEL_ENV_VALUE: + base_def_env_values.append( + ' \\"{}\\": \\"{}\\",'.format(key, _NULL_BAZEL_ENV_VALUE_LITERAL), + ) collect_statements.append("""\ if [[ -n "${{{key}:-}}" ]]; then envs+=("{key}=${key}") @@ -325,7 +332,7 @@ fi )) for key, value in generator_bazel_env.items(): - if value == "\0": + if value == _NULL_BAZEL_ENV_VALUE: collect_statements.append("""\ if [[ -n "${{{key}:-}}" ]]; then envs+=("{key}=${key}") diff --git a/xcodeproj/xcodeproj.bzl b/xcodeproj/xcodeproj.bzl index 021d025f4..2743390f8 100644 --- a/xcodeproj/xcodeproj.bzl +++ b/xcodeproj/xcodeproj.bzl @@ -13,6 +13,10 @@ load( "xcscheme_labels", ) +# Null character is used to represent `None`, since `attr.string_dict` +# requires non-`None` values. +_NULL_BAZEL_ENV_VALUE = "\0" + def _normalize_build_setting(flag): if flag.startswith("//command_line_option:"): return flag @@ -395,15 +399,11 @@ def xcodeproj( bazel_env["BAZELISK_SKIP_WRAPPER"] = None bazel_env = { - # Null character is used to represent `None`, since `attr.string_dict` - # requires non-`None` values. - key: "\0" if value == None else value + key: _NULL_BAZEL_ENV_VALUE if value == None else value for key, value in sorted(bazel_env.items()) } generator_bazel_env = { - # Null character is used to represent `None`, since `attr.string_dict` - # requires non-`None` values. - key: "\0" if value == None else value + key: _NULL_BAZEL_ENV_VALUE if value == None else value for key, value in sorted(generator_bazel_env.items()) }