Skip to content

Commit 6d6b24a

Browse files
committed
starlark_repository: add .local() tag class for on-disk repos
Lets MODULE.bazel point starlark_repository at a workspace-local path instead of an http_archive. Useful when the source already lives on disk — e.g. a git submodule — and we want to skip the network fetch entirely. The bcr-frontend project hits this with ~449 overlay-bzl modules that all derive from the same bazel-central-registry archive; emitting one `.archive(urls=...)` per module trips GitHub's 429 rate limit on cold caches. Changes - extensions/starlark_repository.bzl: new `local` tag class with attrs {name, path, build_directives, build_file_generation, languages, cfgs, imports, imports_out (default "imports.csv"), deleted_files, reresolve_known_proto_imports, importpath}. The extension impl maps user-facing `path` to the underlying `local_path` attr and dispatches to the same starlark_repository repo rule used by `.archive`. - rules/proto/proto_repository.bzl: in `_proto_repository_impl`'s local_path branch, resolve workspace-relative paths against the main workspace root (via Label("@@//:MODULE.bazel").dirname) before passing them to ctx.watch_tree() and fetch_repo's --path arg. Bare strings were otherwise interpreted relative to the external repo's own dir, where the directory doesn't exist. - rules/proto/proto_repository.bzl: in _generate_proto_repository_info, return empty string when imports_out is unset, so a future caller with no imports_out can't trip `exports_files([""])`. Usage: starlark_repository = use_extension( "@build_stack_rules_proto//extensions:starlark_repository.bzl", "starlark_repository", ) starlark_repository.local( name = "bzl.colordiff---1.0.22", path = "data/bazel-central-registry/modules/colordiff/1.0.22/overlay", build_directives = ["gazelle:starlarkrepository_root"], build_file_generation = "clean", languages = ["starlarkrepository"], ) use_repo(starlark_repository, "bzl.colordiff---1.0.22")
1 parent 66bf261 commit 6d6b24a

2 files changed

Lines changed: 62 additions & 7 deletions

File tree

extensions/starlark_repository.bzl

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ def _extension_metadata(
4040
)
4141

4242
def _starlark_repository_impl(module_ctx):
43-
# named_repos is a dict<K,V> where V is the kwargs for the actual
44-
# "starlark_repository" repo rule and K is the tag.name (the name given by the
45-
# MODULE.bazel author)
43+
# named_archives / named_locals are dicts<K,V> where V is the kwargs for
44+
# the underlying "starlark_repository" repo rule and K is the tag.name
45+
# (the name given by the MODULE.bazel author).
4646
named_archives = {}
47+
named_locals = {}
4748

48-
# iterate all the module tags and gather a list of named_archives.
49+
# iterate all the module tags and gather a list of named repos.
4950
#
5051
# TODO(pcj): what is the best practice for version selection here? Do I need
5152
# to check if module.is_root and handle that differently?
@@ -58,13 +59,28 @@ def _starlark_repository_impl(module_ctx):
5859
if hasattr(tag, attr)
5960
}
6061
named_archives[tag.name] = kwargs
62+
for tag in module.tags.local:
63+
kwargs = {
64+
attr: getattr(tag, attr)
65+
for attr in _starlark_repository_local_attrs.keys()
66+
if hasattr(tag, attr)
67+
}
68+
# The user-facing attr is "path"; the underlying repo rule expects
69+
# "local_path" (a sibling of "urls" / "commit" / "version").
70+
kwargs["local_path"] = kwargs.pop("path")
71+
named_locals[tag.name] = kwargs
6172

6273
# declare a repository rule foreach one
6374
for apparent_name, kwargs in named_archives.items():
6475
starlark_repository_repo_rule(
6576
apparent_name = apparent_name,
6677
**kwargs
6778
)
79+
for apparent_name, kwargs in named_locals.items():
80+
starlark_repository_repo_rule(
81+
apparent_name = apparent_name,
82+
**kwargs
83+
)
6884

6985
return _extension_metadata(
7086
module_ctx,
@@ -79,12 +95,40 @@ _starlark_repository_archive_attrs = starlark_repository_attrs | {
7995
}
8096
_starlark_repository_archive_attrs.pop("apparent_name")
8197

98+
# Attrs for the .local() tag class. Excludes archive-only attrs (urls, sha256,
99+
# strip_prefix, type, integrity, canonical_id, auth_patterns, commit, tag,
100+
# vcs, remote, version, sum, replace) and instead takes a single `path`
101+
# (mapped to the underlying rule's `local_path`).
102+
_starlark_repository_local_attrs = {
103+
"name": attr.string(
104+
doc = "The repo name.",
105+
mandatory = True,
106+
),
107+
"path": attr.string(
108+
doc = "Filesystem path (workspace-relative or absolute) to the repository contents.",
109+
mandatory = True,
110+
),
111+
"build_directives": attr.string_list(),
112+
"build_file_generation": attr.string(),
113+
"languages": attr.string_list(),
114+
"cfgs": attr.label_list(allow_files = True),
115+
"imports": attr.label_list(allow_files = True),
116+
"imports_out": attr.string(default = "imports.csv"),
117+
"deleted_files": attr.string_list(),
118+
"reresolve_known_proto_imports": attr.bool(),
119+
"importpath": attr.string(),
120+
}
121+
82122
starlark_repository = module_extension(
83123
implementation = _starlark_repository_impl,
84124
tag_classes = dict(
85125
archive = tag_class(
86126
doc = "declare an http_archive repository that is post-processed by a custom version of gazelle that includes the 'protobuf' language",
87127
attrs = _starlark_repository_archive_attrs,
88128
),
129+
local = tag_class(
130+
doc = "declare a local-path repository that is post-processed by gazelle's starlarkrepository language. Useful when the source already lives on disk (e.g. a git submodule) and we want to avoid network fetches.",
131+
attrs = _starlark_repository_local_attrs,
132+
),
89133
),
90134
)

rules/proto/proto_repository.bzl

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,25 @@ def _proto_repository_impl(ctx):
8484

8585
reproducible = False
8686
if ctx.attr.local_path:
87+
# Resolve relative paths against the main workspace root. Bare strings
88+
# passed to watch_tree() and the --path arg of fetch_repo are otherwise
89+
# interpreted relative to the external repo's own directory, which is
90+
# never what the caller wants.
91+
local_path = ctx.attr.local_path
92+
if not local_path.startswith("/"):
93+
workspace_root = str(ctx.path(Label("@@//:MODULE.bazel")).dirname)
94+
local_path = workspace_root + "/" + local_path
95+
8796
if hasattr(ctx, "watch_tree"):
8897
# https://github.com/bazelbuild/bazel/commit/fffa0affebbacf1961a97ef7cd248be64487d480
89-
ctx.watch_tree(ctx.attr.local_path)
98+
ctx.watch_tree(local_path)
9099
else:
91100
# buildifier: disable=print
92101
print("""
93102
WARNING: go.mod replace directives to module paths is only supported in bazel 7.1.0-rc1 or later,
94-
Because of this changes to %s will not be detected by your version of Bazel.""" % ctx.attr.local_path)
103+
Because of this changes to %s will not be detected by your version of Bazel.""" % local_path)
95104

96-
fetch_repo_args = ["--path", ctx.attr.local_path, "--dest", ctx.path("")]
105+
fetch_repo_args = ["--path", local_path, "--dest", ctx.path("")]
97106
elif ctx.attr.urls:
98107
# HTTP mode
99108
for key in ("commit", "tag", "vcs", "remote", "version", "sum", "replace"):
@@ -461,6 +470,8 @@ package_info(
461470
)
462471

463472
def _generate_proto_repository_info(ctx):
473+
if not ctx.attr.imports_out:
474+
return ""
464475
return """
465476
exports_files(["{imports_out}"])
466477
""".format(

0 commit comments

Comments
 (0)