3.3.4 Target Compatibility
A cross-platform repository typically contains targets that only make sense on certain platforms — a Windows device driver, a Linux kernel module, an iOS-specific UI component. Without any annotation, bazel build //... attempts every target and fails on the first one that cannot compile for the current platform. The target_compatible_with attribute solves this by declaring which platforms a target supports, letting Bazel skip the rest automatically1.
Declaring Compatibility
Every build rule accepts target_compatible_with as a list of compatibility conditions. The common case is a list of constraint_value targets from the 3.3.3 Constraint Values vocabulary: the target platform must satisfy all listed constraints for the target to be considered compatible2. Bazel also accepts config_setting labels in the same list. Those add configuration-level checks, so the target is compatible only when the target platform satisfies the listed constraints and the target configuration matches the listed config_settings3:
cc_library(
name = "win_driver_lib",
srcs = ["win_driver_lib.cc"],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
],
)
:win_driver_lib is compatible only with 64-bit Windows. An empty list (or omitting the attribute entirely) means the target is compatible with every platform.
Use config_setting entries when compatibility depends on a build configuration value, not just on the platform's constraint values. For example, a target can require a project build setting, a built-in flag value, or a compound platform condition expressed through config_setting. Keep that distinction sharp: constraint_value answers "what platform is this?", while config_setting answers "does this configuration match this named condition?"
The same direct declaration appears in the maintainer-workspace app BUILD file, where separate admin tools declare Linux-only and macOS-only compatibility.
Wildcard Builds vs Explicit Requests
The skip behavior depends on how the target was requested2:
Wildcard patterns (//..., //:all) silently skip incompatible targets. This is the primary use case — it makes bazel build //... and bazel test //... safe to run regardless of the developer's machine:
bazel build --platforms=//:myplatform //...
Incompatible targets are omitted from the build without producing errors.
Explicit requests produce a clear error. If you name an incompatible target directly, Bazel tells you why it cannot build:
bazel build --platforms=//platforms:linux //app:mac_only
ERROR: Analysis of target '//app:mac_only' failed; build aborted: Target //app:mac_only is incompatible and cannot be built, but was explicitly requested.
Dependency chain:
//app:mac_only (...) <-- target platform (//platforms:linux) didn't satisfy constraint //platforms:demo_macos
The target exists. The active target platform does not satisfy its target_compatible_with constraints. That makes this an analysis-time platform rejection, not a label-resolution problem.
This distinction is intentional: wildcards are exploratory ("build everything that works"), while explicit requests express intent ("I specifically want this target"). The flag --skip_incompatible_explicit_targets relaxes the explicit-request error if needed2.
For a focused reproduction of the wildcard-versus-explicit behavior, the target-compatibility snippet defines Linux-only, macOS-only, and dependent targets in one small package.
Transitivity
Incompatibility propagates through the dependency graph. If target B is incompatible with the current platform and target A depends on B, then A is also considered incompatible — even if A itself has no target_compatible_with restrictions1. This means marking a low-level library as platform-specific automatically shields all its dependents from wildcard builds on unsupported platforms. The runnable form of this propagation is the depends_on_mac_only filegroup, which carries no compatibility attribute itself but inherits incompatibility from its mac-only dependency.
Expressive Constraints with select()
A plain target_compatible_with list expresses AND logic: the platform/configuration pair must satisfy all listed entries. For OR or NOT logic, combine target_compatible_with with 3.3.1 Configurable Attributes (select()) and the special @platforms//:incompatible constraint value — a sentinel that no platform ever satisfies2.
OR: compatible with Linux or macOS only
cc_library(
name = "unixish_lib",
srcs = ["unixish_lib.cc"],
target_compatible_with = select({
"@platforms//os:osx": [],
"@platforms//os:linux": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
)
When targeting macOS or Linux, the constraint list is empty (compatible with everything). For any other platform, the list contains @platforms//:incompatible, making the target incompatible2.
NOT: compatible with everything except ARM
cc_library(
name = "non_arm_lib",
srcs = ["non_arm_lib.cc"],
target_compatible_with = select({
"@platforms//cpu:arm": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)
The pattern is the same — swap which branches get the empty list and which get the incompatible sentinel1.
For readability when multiple conditions map to the same result, bazel-skylib's selects.with_or() reduces duplication2.
Test Suites
Incompatible tests inside a test_suite are skipped when the suite is specified on the command line with --expand_test_suites (the default). The test_suite behaves like a wildcard — individual incompatible tests drop out silently. With --noexpand_test_suites, Bazel treats the suite itself as a single target, which becomes incompatible if any member is incompatible2.
Detecting Incompatible Targets
To programmatically identify which targets are incompatible for a given platform, use bazel cquery with a Starlark output format that checks for IncompatiblePlatformProvider2:
# compatible_targets.cquery
def format(target):
if "IncompatiblePlatformProvider" not in providers(target):
return target.label
return ""
bazel cquery //... --output=starlark --starlark:file=compatible_targets.cquery
This filters the output to only compatible targets — useful for CI scripts that need to know exactly what will build on a given platform.
The provider this filter inspects is not something rule implementations construct themselves — Bazel attaches it when it decides a target is incompatible. The marker's mechanics, the supported "programmatic" levers a rule author actually has (configurable target_compatible_with, optional toolchains), and the CI patterns this filter enables are covered in 4.6.7 IncompatiblePlatformProvider as part of the broader 4.6 Toolchains & Platform Resolution architecture.
target_compatible_with declares which platform/configuration combinations a target supports. Wildcard builds (//...) silently skip incompatible targets. Explicit requests produce an error. Combine with select() and @platforms//:incompatible for OR and NOT logic. Incompatibility is transitive — marking a library as platform-specific automatically shields all its dependents.
Check your understanding · 3 questions
1.A target B has target_compatible_with = ['@platforms//os:windows']. Target A depends on B and has no target_compatible_with. You run 'bazel build //...' on Linux. What happens?
Select one answer
2.True or false about target_compatible_with behavior:
Choose True or False for each sentence
3.How can you programmatically identify which targets are incompatible for a given platform using cquery?
Select one answer
Footnotes
-
Incompatible target skipping — design motivation, transitivity, and select()-based OR/NOT patterns ↩1 ↩2 ↩3
-
Platforms — target_compatible_with reference, @platforms//:incompatible sentinel, wildcard vs explicit behavior, test_suite interaction, cquery detection ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Common definitions —
target_compatible_withacceptsconstraint_values andconfig_settings. Constraints must be satisfied by the target platform and config settings must match the target configuration. ↩