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:

Won't 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.

Reproduce this error

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.

key takeaway

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

Naming an incompatible target explicitly (e.g., bazel build //:win_target on Linux) silently skips it.
The @platforms//:incompatible sentinel value is never satisfied by any platform.
target_compatible_with alone can express OR logic (compatible with Linux or macOS) without select().

3.How can you programmatically identify which targets are incompatible for a given platform using cquery?

Select one answer

0 of 3 answered

Footnotes

  1. Incompatible target skipping — design motivation, transitivity, and select()-based OR/NOT patterns 1 2 3

  2. 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

  3. Common definitionstarget_compatible_with accepts constraint_values and config_settings. Constraints must be satisfied by the target platform and config settings must match the target configuration.