This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Bazel Gazelle transitive visibility

Generate package dependency restrictions from TOML path mappings

    This standalone Gazelle extension maintains package(transitive_visibility = ...) from a TOML mapping of workspace-relative directories to package groups. The default configuration is tools/transitive_visibility/transitive_visibility.toml.

    [paths]
    "." = "//:transitive_consumers"
    "infra" = "//infra:transitive_consumers"
    "tools" = "//tools:transitive_consumers"
    "tools/nogo" = "//tools:build_api_consumers"
    

    Mappings include descendant directories. The longest directory-prefix match wins; infra does not match infrastructure. Paths are canonical relative directories, with "." denoting the workspace root; globs and parent paths are not accepted. Labels must be absolute. Package groups remain in their owning BUILD files and define the permitted consumers.

    Use -transitive_visibility_config=path/to/policy.toml to select another file. Relative filenames resolve from Gazelle’s repository root. A missing default configuration leaves policy unmanaged; an explicitly selected missing file is an error. Malformed TOML, unknown fields, empty mappings, invalid paths and invalid labels fail before BUILD output is written. Obsolete gazelle:transitive_visibility comments are rejected with a migration message.

    Only the transitive_visibility attribute is managed. Other attributes and comments are preserved, and managed package() calls in existing files appear after loads and before rules. Unmatched packages retain their manual declarations. The plugin does not create BUILD files in otherwise empty directories. Register it after other generators so it covers the packages they create. Gazelle’s normal ignored-directory and nested-workspace boundaries apply; parent configurations are never searched.

    Register @bazel_gazelle_enforce_transitive_visibility//pkg/gazelle in the languages of a gazelle_binary. //cmd/gazelle provides the plugin-only executable. This module’s own configuration is transitive_visibility.toml:

    bazel run //cmd/gazelle -- -transitive_visibility_config=transitive_visibility.toml
    

    Bazel enforcement is separate from generation. Enable --experimental_enforce_transitive_visibility=true and use Bazel 9.3.1 or later. Bazel checks target dependencies for the entire package; it cannot distinguish target roles inside that package and does not govern Starlark load() statements.

    Direct target and source-file restrictions work. An integration experiment also found that an intermediary with its own permissive transitive_visibility declaration can lose an inherited restriction: a forbidden consumer of its filegroup succeeds. Removing the intermediary’s declaration makes the same indirect dependency fail. Consequently, generated declarations are not a guarantee of complete indirect dependency enforcement in this Bazel release. The extension emits the requested policy; it does not repair Bazel’s analysis.

    The native check is also broader than an artifact-only dependency policy. Its 9.3.1 validator does not exempt execution-tool attributes; declaring a dependency with cfg = "exec" does not itself exempt it. Package groups cannot express “allow this executable as a build tool, but forbid it as a runtime input.” Explicit build-support package exceptions are therefore an approximation. Starlark load() edges are separate. Use --notool_deps when querying the ordinary dependency graph; that query option does not change build enforcement.

    From this module, run bazel test //... to exercise the real Gazelle command. The tests preserve generated BUILD files and a summary under Bazel’s undeclared test outputs.

    Gazelle 0.54.0 appends package() after generated rules when it creates a new BUILD file. A subsequent run moves it after the loads; further runs are stable. The repository uses unmodified upstream Gazelle and does not patch this behavior.