Use Bazel to create GitHub CODEOWNERS.
CODEOWNERS on GitHub is flawed in the way that there can only be one CODEOWNERS file.
rules_codeowners
allows you to define the CODEOWNERS at all levels in the repo, and then generating the final CODEOWNERs file.
See also: README_DOCS.md
rules_codeowners is available on the Bazel Central Registry:
Add this to your MODULE.bazel
:
bazel_dep(name = "rules_codeowners", version = "0.2.1")
... or if you're not using modules, add this to WORKSPACE
:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_CODEOWNERS_VERSION = "de6749190dcc5209427799b1e9777f79ab623702"
RULES_CODEOWNERS_SHA = "9277d41fefe2eded54ab1d3eeaebbef9ad28713291373a635079868980c6ead0"
http_archive(
name = "rules_codeowners",
strip_prefix = "rules_codeowners-%s" % RULES_CODEOWNERS_VERSION,
sha256 = RULES_CODEOWNERS_SHA,
url = "https://github.com/zegl/rules_codeowners/archive/%s.zip" % RULES_CODEOWNERS_VERSION,
)
In BUILD files:
load("@rules_codeowners//tools:codeowners.bzl", "codeowners", "generate_codeowners")
# Define one or many codeowners, should be added at multiple levels in the repo
codeowners(
name = "team_rule",
# Set either team or teams to assign the GitHub team ownership
team = "@org/foo",
# teams = ["@org/team1", "@org/team2"],
visibility = ["//visibility:public"],
# Optional rules below
pattern = "*.js", # Adds pattern to the end of the path if this rule label is
# //foo/bar:codeowner, this would create an entry for /foo/bar/*.js
patterns = ["*.js", "*.ts"] # Same as pattern, but generates multiple rows, one for each pattern.
# Only one of pattern and patterns can be set at the same time.
)
# The generate_codeowners rule ties together multiple codeowners (and generate_codeowners) rules, and
# generates the final CODEOWNERS file.
#
# A generate_codeowners can use another generate_codeowners in the `owners` list,
# to effectively delegate access to modify the CODOWNERS in a part of the code-tree.
generate_codeowners(
name = "generate_codeowners",
owners = [
":team_rule",
],
)
The generate_codeowners
rule (//:generate_codeowners
), can be built with Bazel to create
the complete CODEOWNERS file, the generated file will be located at bazel-bin/generate_codeowners.out
by default.
bazel build //:generate_codeowners
It's not possible for Bazel to output files to the workspace, but it is possible to compare the current CODEOWNERS
with the generated version, to remind you that it's out of date with a sh_test
.
sh_test(
name = "validate_codeowoners_up_to_date",
srcs = ["@rules_codeowners//tools:diff.sh"],
args = [
"$(location :generate_codeowners.out)",
"$(location CODEOWNERS)",
],
data = [
"CODEOWNERS",
":generate_codeowners.out",
],
)
To automatically set all codeowners
as owners in a generate_codeowners
, the following script that utilizes bazel query
and buildozer
can be used.
#!/usr/bin/env bash
# target to the generate_codeowners, eg "//.github:gen_codeowners"
readonly target=$1
readonly BAZEL=$(which bazel)
readonly BUILDOZER=$(which buildozer)
readonly new_owners=$(
# Query for all codeowners() rules (anchor at the front to avoid match on generate_codeowners rule)
$BAZEL query --output=label 'kind("^codeowners rule", //...)' |
# Print the length of each label at the front of the line
awk '{ print length, $0 }' |
# Sort shortest-first, so that the root //:OWNERS ends up first in CODEOWNERS
sort -n -s |
# 43 label -> "label"
awk '{ print "\x22" $2 "\x22" }' |
# comma-separated
tr '\n' ','
)
readonly command="set owners [${new_owners}]|${target}"
echo "$command" | $BUILDOZER -f -
To verify that all codeowners
are targeted by a generate_codeowners
, a script like the one below can be used.
#!/usr/bin/env bash
# target to the generate_codeowners, eg "//.github:gen_codeowners"
readonly target=$1
readonly TEEFILE=$(mktemp)
readonly BAZEL=$(which bazel)
$BAZEL query "kind(codeowners, //...) except deps(${target})" 2>&1 | tee "$TEEFILE" | grep "Empty results"
EXIT=$?
if [ "$EXIT" -eq 1 ]; then
cat "$TEEFILE"
echo "--> All codeowners rules must be depended on by ${target}"
echo "--> Add the codeowners as a dependency, or delete the target"
exit 1
fi
exit 0