bazel-gazelle

repository·master·Indexed 23 days ago

https://github.com/bazel-contrib/bazel-gazelle

A build file generator for Bazel projects that automates the management of BUILD files. While it natively supports Go and Protocol Buffers, it is extensible to other languages like Java, Python, Rust, and TypeScript. It includes features such as Autogazelle for automatic generation, lazy indexing for large repositories, and the gazelle_test rule to verify that build files are up-to-date.

Tokens
23.9K
Snippets
45
Records
125
Agent score
75%

What's inside bazel-gazelle

  1. What is Gazelle?

    master

    Gazelle is a build file generator for Bazel projects. It automates the creation of new BUILD.bazel files for projects following language conventions and updates existing build files to include new sources, dependencies, and options.

    While it natively supports Go and Protocol Buffers, it can be extended to support many other languages (such as Java, Python, Rust, and TypeScript) via third-party extensions.

  2. How directory indexing works in Gazelle

    master

    Gazelle's directory traversal behavior is controlled by several flags and directives:

    • Positional Arguments: Gazelle always visits directories specified as positional arguments on the command line. If none are provided, it starts at the repository root.
    • Recursion (-r=true): Enabled by default. If true, Gazelle recursively visits subdirectories.
    • Eager Indexing (-index=all): Enabled by default. Gazelle visits all directories.
    • Lazy Indexing (-index=lazy): Gazelle only visits directories requested by language extensions via GenerateResult.RelsToIndex during the Generate stage.
    • No Indexing (-index=none): Gazelle does not visit additional directories beyond those explicitly requested.

    Gazelle also visits parent directories within the repository to ensure # gazelle:exclude directives are correctly applied.

  3. Gazelle Terminology: Rule vs Target

    master

    Gazelle uses terminology that matches Bazel's internal source code, which may differ from standard Bazel documentation:

    • Rule: In Gazelle, this refers to a declaration in a BUILD file (e.g., go_binary(name = "lib", ...)).
    • Rule Kind: The type of rule being declared (e.g., go_binary).

    Note: While Bazel documentation often calls these Targets, Gazelle refers to them as Rules to maintain consistency with its extension API.

  4. Protect build file sections with `# keep` comments

    master

    Gazelle supports # keep comments to prevent specific parts of a build file from being modified or removed during the generation process. This is useful when you need to manually add dependencies that Gazelle cannot automatically resolve (e.g., dependencies on generated files).

    # keep can be used in three ways:

    1. Before a rule: Protects the entire rule.
    2. Before an attribute: Protects a specific attribute within a rule.
    3. After a string within a list: Protects a specific element in a list (most common for deps).

    Supported formats:

    • # keep (literal)
    • # keep: <description> (description prefixed by a colon)
    go_library(
        name = "go_default_library",
        srcs = ["magic.go"],
        visibility = ["//visibility:public"],
        deps = [
            "@com_github_example_gen//:go_default_library",  # keep
            "@com_github_example_gen//a/b/c:go_default_library",  # keep: this is also important
        ],
    )
  5. How Gazelle merges machine-generated and human-written rules

    master

    To preserve human-written content while updating machine-generated parts, Gazelle extensions do not modify the syntax tree directly. Instead, the Language.GenerateRules method returns two lists:

    • Gen list: Rules that should exist in the BUILD file.
    • Empty list: Rules that should be deleted if present.

    Gazelle then uses merger.MergeFile to reconcile these lists with the existing BUILD file. The merging logic is governed by the Kinds map returned by the extension's Kinds method, which defines which attributes are matchable (used to identify a rule) and which are mergeable (used to combine values).

  6. How lazy indexing works in Gazelle

    master

    Lazy indexing allows Gazelle to run quickly by avoiding a full scan of all build files in a repository while still supporting index-based dependency resolution. This enables millisecond-level updates for specific directories.

    Requirements:

    • User Configuration: Users must point to directories that might contain libraries based on import strings.
    • Language Extension Support: Extensions must implement logic to interpret configuration directives and map import strings to directory paths.

    Example (Go): If a user has a module dependency example.com/b located at replace/b, they can use the go_search directive in their top-level build file to tell Gazelle how to resolve imports:

    # gazelle:go_search replace/b example.com/b

    When Gazelle encounters an import like example.com/b/c, it will index replace/b/c and make those targets available for resolution.

    Implementation for Language Extensions: To support lazy indexing, an extension should:

    1. Register the directive (e.g., go_search) in the KnownDirectives and Configure methods of the Configurer implementation.
    2. Convert import strings (e.g., example.com/b/c) into directory paths (e.g., replace/b/c).
    3. Return these paths via GenerateResult.RelsToIndex within the Language.GenerateRules method.
  7. Configure Gazelle with directives

    master

    Gazelle can be configured using directives, which are top-level comments in your Bazel build files. Directives follow the format # gazelle:key value.

    Directives apply to the directory where they are defined and all subdirectories. For example, setting a prefix in the root directory affects the entire project.

    load("@io_bazel_rules_go//go:def.bzl", "go_library")
    
    # gazelle:prefix github.com/example/project
    # gazelle:build_file_name BUILD,BUILD.bazel
    
    go_library(
        name = "go_default_library",
        srcs = ["example.go"],
        importpath = "github.com/example/project",
        visibility = ["//visibility:public"],
    )
  8. The Generate stage and extension method execution order

    master

    During the Generate stage, Gazelle calls extension methods in a specific order to manage rule creation and merging:

    1. Configure: Called in pre-order for every directory visited. Extensions use this to read directives from BUILD files. This is the only method called in pre-order; all others are post-order.
    2. Fix: Called in directories with existing BUILD files to transform or fix deprecated rule usage.
    3. GenerateRules: Called in each directory. It returns rules that should exist and rules that should be removed. Note: This method must not modify the rules parsed from the BUILD file directly.
    4. merger.MergeFile: Combines generated rules with existing ones.
      • Unmatched rules are added to the end of the file.
      • Attributes are merged: 'mergeable' attributes managed by Gazelle can overwrite existing values (unless marked with # keep), while non-mergeable attributes are only set if they don't exist.
      • Rules returned in an empty list by GenerateRules can be deleted if not marked with # keep.
    5. Imports: Called on each rule after merging to build the in-memory index used for the Resolve stage. This is skipped if -index=none is used.
  9. Configure the go_deps Bzlmod extension

    master

    The go_deps extension is used in Bzlmod to manage Go module dependencies. You first declare the extension using use_extension and then use its various tags to define modules, overrides, and configurations.

    Common workflow:

    1. Use use_extension to load go_deps.
    2. Use from_file to import dependencies from go.mod or go.work (recommended).
    3. Use module to declare individual dependencies manually.
    4. Use module_override or archive_override to apply patches or change source locations.
    5. Use config to set global behavior like debug mode or environment variables.
    go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
  10. How Gazelle updates BUILD files

    master

    Gazelle updates BUILD files through a four-stage pipeline:

    1. Load: Parses existing BUILD files and collects directory metadata (files, subdirectories), respecting # gazelle:exclude and .bazelignore. This metadata is cached in memory.
    2. Generate: The core stage where extensions are called to apply configuration, fix deprecated usage, generate new rules, and merge them with existing rules. This stage also builds an in-memory index of imports for dependency resolution.
    3. Resolve: Uses the index built during the Generate stage to map imports in source files to Bazel labels in deps attributes.
    4. Write: Formats the modified BUILD files (using build.Format) and saves them to disk or prints them based on the -mode flag.
  11. How Gazelle resolves dependencies

    master

    Gazelle resolves library import strings (e.g., import "golang.org/x/sys/unix") to Bazel labels (e.g., @org_golang_x_sys//unix:go_default_library) using the following priority:

    1. Standard Library: No explicit dependency is written (e.g., Go's fmt).
    2. # gazelle:resolve Directive: If a directive matches the import, the provided label is used.
    3. Proto Rules: If enabled, special rules map Well Known Types and specific protobuf libraries (like ptypes or descriptor) to specific rules. This can be disabled via # gazelle:proto disable_global or -proto disable_global.
    4. Library Index: If the import is in the indexed library rules, it resolves to that library.
      • -index=all: Builds a full index of the repo (slow).
      • -index=lazy: Uses language extensions to visit specific directories (fast).
    5. Prefix Convention: If -index=none is used and the import matches the current go_prefix, Gazelle generates a label following a convention (e.g., //src/foo/bar:go_default_library).
    6. External/Static/Vendored Modes:
      • external (Default): Transforms imports into external repository labels (e.g., @org_golang_x_sys//...). It assumes a matching go_repository exists in WORKSPACE but does not verify it.
      • static: Similar to external, but skips unknown imports instead of attempting network resolution. This is the default for go_repository rules.
      • vendored: Transforms imports into labels within the vendor directory (e.g., //vendor/golang.org/x/sys/unix:go_default_library).
  12. How rule matching and merging works in merger.MergeFile

    master

    When merger.MergeFile processes rules, it follows these steps:

    1. Matching: It attempts to match a generated rule with an existing rule. A match occurs if:
      • The rule has the same kind and name (e.g., go_binary with name = "server").
      • OR one of its matchable attributes (defined in Kinds) has the same value (e.g., go_library with importpath = "example.com/foo").
      • OR the rule kind's MatchAny flag is set in the Kinds map.
    2. Unmatched Rules: If no match is found, rules from the Gen list are added, and rules from the Empty list are ignored.
    3. Matched Rules: If a match is found, rule.MergeRules is called:
      • New attributes: Added to the existing rule.
      • Missing attributes: If an attribute is in the existing rule but not the new one, it is deleted if it is marked as mergeable in Kinds, otherwise it is preserved (useful for human-written attributes).
      • Conflicting attributes: If an attribute exists in both:
        • If not mergeable: The existing (human-written) value is preserved.
        • If mergeable: The values are merged (e.g., merging lists). Extension authors can implement custom logic via the rule.Merger interface.
    4. Deletion: If a rule becomes "empty" (no non-empty attributes like srcs or deps remain) after merging with an Empty list rule, the rule is deleted from the BUILD file.