How to Modify Attributes on Generated Bazel Targets
September 10, 2026 ยท View on GitHub
Table of Contents
What this is
rules_swift_package_manager generates all of the Bazel declarations for the Swift packages that it
builds. The swift_deps module extension provides tags, modeled on buildozer commands, that edit
attributes on those generated declarations from your root MODULE.bazel. There are four verbs:
- set replaces (or creates) an attribute.
- set_select replaces (or creates) an attribute with a
select(). - add appends values to a list attribute.
- add_select appends a
select()to a list attribute.
Nothing is parsed or guessed. Each set and set_select verb has one tag per value type, and you
pick the tag that matches the type the attribute wants:
| Tag | Value type |
|---|---|
swift_deps.bazel_target_set_bool | bool |
swift_deps.bazel_target_set_int | int |
swift_deps.bazel_target_set_string | string |
swift_deps.bazel_target_set_string_list | list of string |
swift_deps.bazel_target_set_string_dict | dict of string to string |
swift_deps.bazel_target_set_select_bool_dict | select() with bool branches |
swift_deps.bazel_target_set_select_int_dict | select() with int branches |
swift_deps.bazel_target_set_select_string_dict | select() with string branches |
swift_deps.bazel_target_set_select_string_list_dict | select() with list branches |
swift_deps.bazel_target_add | list of string |
swift_deps.bazel_target_add_select | select() with list branches |
This is a use-at-your-own-risk escape hatch. It reaches directly into generated output, there is no allowlist of attribute names, and nothing checks that the attribute you name makes sense for the rule you are editing.
If you need these tags, rules_swift_package_manager is probably missing a real feature. Please
file an issue describing what you were trying to do, even if the escape hatch unblocked you, so
that the ruleset can support it properly.
Find the target to modify
Every Swift package is generated into its own repository named @swiftpkg_<identity>, where
<identity> is the package identity from the manifest with - replaced by _. For example, the
swift-argument-parser package is generated into @swiftpkg_swift_argument_parser.
Inside that repository, each Swift package target is generated as two declarations in the root package:
<Name>.rspmis the publicly advertised target. This is what you depend on from your ownBUILDfiles, and it is usually just an alias or a thin wrapper.<Name>.rspm.__implis the declaration that actually compiles or imports the code. Attributes such ascopts,defines, andalwayslinklive here.
Modification tags almost always name the .__impl declaration.
To see what was actually generated:
# List the generated declarations, with their attributes.
$ bazel query --output=build 'kind(rule, @swiftpkg_example//:all)'
# Or read the generated build file directly.
$ find "$(bazel info output_base)/external" -maxdepth 1 -name '*swiftpkg_example'
All generated declarations live in the root package of the generated repository, so the target
value must be of the form @repo_name//:target_name.
The target value looks like a label, but it is not resolved as one. The repository portion is the
name of the generated repository as this ruleset named it (e.g. swiftpkg_swift_log). An alias
declared with use_repo (e.g. use_repo(swift_deps, log = "swiftpkg_swift_log")) is not accepted,
and neither is Bazel's canonical repository name, so the @@ prefix is rejected. The name does not
need to be imported with use_repo for a modification to apply.
The verbs
Setters
A setter replaces the attribute, creating it if the generated declaration does not have it. Pick the tag whose type matches the value you want to write.
swift_deps.bazel_target_set_bool(
attr = "alwayslink",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
value = False,
)
swift_deps.bazel_target_set_int(
attr = "shard_count",
target = "@swiftpkg_example//:ExampleTests.rspm.__impl",
value = 4,
)
swift_deps.bazel_target_set_string(
attr = "module_name",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
value = "CustomExample",
)
swift_deps.bazel_target_set_string_list(
attr = "copts",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
value = ["-DEXAMPLE_FEATURE"],
)
swift_deps.bazel_target_set_string_dict(
attr = "env",
target = "@swiftpkg_example//:ExampleTests.rspm.__impl",
value = {"EXAMPLE_MODE": "strict"},
)
An empty value is allowed, so value = [] clears a list attribute and value = "" writes an empty
string.
Select setters
A select setter replaces the attribute with a select(). No //conditions:default branch is added
for you, so add one yourself if the attribute needs a value in unmatched configurations.
bazel_target_set_select_string_dict and bazel_target_set_select_string_list_dict write their
branch values as they are given: a string and a list of string, respectively.
swift_deps.bazel_target_set_select_string_list_dict(
attr = "copts",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
values = {
"//:release_build": ["-DEXAMPLE_RELEASE_FEATURE"],
"//conditions:default": [],
},
)
swift_deps.bazel_target_set_select_string_dict(
attr = "module_name",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
values = {
"//:release_build": "ExampleRelease",
"//conditions:default": "Example",
},
)
bazel_target_set_select_bool_dict and bazel_target_set_select_int_dict take string branch
values because Bazel has no bool or int dictionary attribute, and convert them for you. A
bool branch must be exactly True or False, spelled the way Starlark spells them, and an int
branch must be digits with an optional leading -. Anything else fails when the module extension is
evaluated, with an error naming the target, the attribute, the condition and the bad value.
swift_deps.bazel_target_set_select_bool_dict(
attr = "alwayslink",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
values = {
"//:release_build": "True",
"//conditions:default": "False",
},
)
swift_deps.bazel_target_set_select_int_dict(
attr = "shard_count",
target = "@swiftpkg_example//:ExampleTests.rspm.__impl",
values = {
"//:release_build": "8",
"//conditions:default": "2",
},
)
bazel_target_add
Appends to a list attribute, creating the attribute if it is absent. Unlike the setters, this verb is
list-only and requires at least one value. The values land after the generated values, so options
that follow last-option-wins semantics (e.g. copts) override what rules_swift_package_manager
generated.
swift_deps.bazel_target_add(
attr = "copts",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
values = ["-DEXAMPLE_FEATURE"],
)
bazel_target_add_select
Appends a select() to a list attribute, preserving the generated value. The attribute renders as
<generated> + select({...}). Branch values are lists, and a //conditions:default branch with no
values is added when you do not provide one.
swift_deps.bazel_target_add_select(
attr = "copts",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
values = {"//:release_build": ["-DEXAMPLE_RELEASE_FEATURE"]},
)
Rules
- Root module only. A non-root module that declares one of these tags fails the build.
- At most one setter per target and attribute. You may declare only one
bazel_target_set_*orbazel_target_set_select_*tag for a given target attribute, across all nine of them. Theaddverbs are unlimited. - Ordering is by verb, not by declaration order. The setter is applied first, then every
bazel_target_addin declaration order, then everybazel_target_add_selectin declaration order. - Setters accept empty values; the other verbs do not.
value = [],value = ""andvalue = {}are all fine and are written as-is.bazel_target_addrequires at least one value, and everyselect()verb requires at least one condition. - The
addverbs are list-only. They take alistofstring(or aselect()whose branches are lists). To write a scalar, adict, or a scalar-branchedselect(), use a setter. select()keys are canonicalized. Keys must be absolute labels. A key that is relative to the main repository (e.g.//:release_build) is rewritten to its canonical form (e.g.@@//:release_build) so that it still resolves from inside the generated repository. Make sure theconfig_settingis visible to external repositories.select()keys may not use an apparent repository name. A key such as@some_repo//:settingis rejected. An apparent name (a single@) is resolved using the repository mapping of whichever repository contains the label, and the key ends up inside a generated repository, so it would resolve againstrules_swift_package_manager's mapping instead of yours. Use a main-repository-relative label (//:setting), which is canonicalized for you, or a canonical label (@@some_repo+//:setting).- Values are emitted verbatim; labels in them are not remapped. A value is written into the
generated
BUILD.bazelfile as-is, so a label-valued string such as//:my_libindepsresolves inside the generated repository, not in your root module. Use@@//:my_libfor a target in the main repository, or a canonical@@repo+//:targetlabel for one in another repository. - The
nameattribute may not be modified. The name of a generated declaration is written separately from its other attributes, so modifying it would emit a duplicatenamekeyword. - Incompatible with a complete build file override. These tags cannot be combined with
configure_package(build_file = ...)for the same package, because that override bypasses the generated declarations entirely. - Errors fail fast. An unknown repository name lists the repositories that were generated, an unknown target name lists the declarations available in that repository, and conflicting setters list the conflicting target attributes.
- Attribute names are not validated. An attribute that the generated rule does not understand
fails later, when Bazel loads the generated
BUILD.bazelfile, with Bazel's own error message.
Real-world examples
Disabling alwayslink on a static xcframework import
rules_swift_package_manager sets alwayslink = True on every generated
apple_static_xcframework_import. A few prebuilt SDKs ship an archive containing an object that
nothing references and whose symbols nothing can resolve. Force-loading pulls it in and the link
fails, even though the same SDK links fine under Swift package manager and Xcode. See
issue #2445.
swift_deps.bazel_target_set_bool(
attr = "alwayslink",
target = "@swiftpkg_facebook_ios_sdk//:FBAudienceNetwork.rspm.__impl",
value = False,
)
Adding compiler options to a single target
Sometimes one package target needs a compile-time flag that the manifest does not express, such as compiling production-only bookkeeping out of a single runtime target without touching its siblings. See pull request #2439.
swift_deps.bazel_target_add(
attr = "copts",
target = "@swiftpkg_example//:ExampleTarget.rspm.__impl",
values = ["-DEXAMPLE_DISABLE_BOOKKEEPING"],
)
Caveat
The names and the layout of the generated targets are implementation details. They can change
between releases of rules_swift_package_manager, which means that your overrides may need to be
updated when you upgrade. This is another reason to file an issue instead of relying on these tags
long-term.