Gazelle started out as a build file generator for Go projects, but it can be extended to support other languages and custom sets of rules.
To extend Gazelle, you must do three things:
- Write a go_library with a function named
NewLanguagethat provides an implementation of the Language interface. This interface provides hooks for generating rules, parsing configuration directives, and resolving imports to Bazel labels. By convention, the library's package name should match the language (for example,protoorbzl). - Write a gazelle_binary rule. Include your library in the
languageslist. - Write a gazelle rule that points to your
gazelle_binary. When you runbazel run //:gazelle, your binary will be built and executed instead of the default binary.
To write tests for your gazelle extension, you can use gazelle_generation_test, which will run a gazelle binary of your choosing on a set of test workspaces.
Moved to /README.rst
Gazelle itself is built using the model described above, so it may serve as an example.
//language/proto:go_default_library and //language/go:go_default_library both implement the Language interface. There is also //internal/gazellebinarytest:go_default_library, a stub implementation used for testing.
//cmd/gazelle is a gazelle_binary rule that includes both of these
libraries through the DEFAULT_LANGUAGES list (you may want to use
DEFAULT_LANGUAGES in your own rule).
load("@bazel_gazelle//:def.bzl", "DEFAULT_LANGUAGES", "gazelle_binary")
gazelle_binary(
name = "my_gazelle_binary",
languages = [
"@rules_python_gazelle_plugin//python", # Use gazelle from rules_python.
"@bazel_gazelle//language/go", # Built-in rule from gazelle for Golang.
"@bazel_gazelle//language/proto", # Built-in rule from gazelle for Protos.
# Any languages that depend on Gazelle's proto plugin must come after it.
"@external_repository//language/gazelle", # External languages can be added here.
],
visibility = ["//visibility:public"],
)This binary can be invoked using a gazelle rule like this:
load("@bazel_gazelle//:def.bzl", "gazelle")
# gazelle:prefix example.com/project
gazelle(
name = "gazelle",
gazelle = ":my_gazelle_binary",
)You can run this with bazel run //:gazelle.
Lazy indexing lets Gazelle run quickly without needing to read all build files in a repo while still supporting index-based dependency resolution. This allows Gazelle to update build files for specific directories in milliseconds rather than seconds or minutes.
Lazy indexing requires a small amount of user configuration, pointing to directories that may contain libraries based on import strings read from source files. It also requires support from language extensions to interpret that configuration. Most language extensions should implement this, though the implementation will be a bit different for each language.
As an extended example, suppose a Go user has copied their module dependency
example.com/b into the directory replace/b. From some other directory a,
they import the package example.com/b/c. They then run
gazelle update -r=false -index=lazy a to generate a build file for a with
lazy indexing enabled.
In this configuration, Gazelle doesn't automatically index replace/b, so
the user must add a directive to their top-level build file:
# gazelle:go_search replace/b example.com/bThis directive is interpreted by the Go extension. The user is telling Gazelle
to index the directory replace/b/<suffix> when it sees a Go package imported
with the string example.com/b/<suffix>. So in our example, when Gazelle sees
example.com/b/c, it indexes replace/b/c and makes all library targets
available for dependency resolution, including non-Go libraries that happen to
be there.
To support this, the Go extension needs to:
- Support the
go_searchdirective in theKnownDirectivesandConfiguremethods of theConfigurerimplementation. This directive may be repeated and applies in subdirectories. - Convert an import string like
example.com/b/cinto a list of directory paths likereplace/b/c. - Return the directory paths through
GenerateResult.RelsToIndexin theLanguage.GenerateRulesmethod.
Other extensions should follow a similar approach, though there are likely to
be differences in how *_search directives and import strings are interpreted.
For example, the protobuf and C++ extensions have proto_search and cc_search
directives which are similar to each other but not to Go: both languages
import libraries by file name and have similar conventions.
The proto extension (//language/proto:go_default_library) gathers metadata
from .proto files and generates proto_library rules based on that metadata.
Extensions that generate language-specific proto rules (e.g.,
go_proto_library) may use this metadata.
For API reference, see the proto godoc.
To get proto configuration information, call proto.GetProtoConfig. This is mainly useful for discovering the current proto mode.
To get information about proto_library rules, examine the OtherGen
list of rules passed to language.GenerateRules. This is a list of rules
generated by other language extensions, and it will include proto_library
rules in each directory, if there were any. For each of these rules, you can
call r.PrivateAttr(proto.PackageKey) to get a proto.Package record. This
includes the proto package name, as well as source names, imports, and options.