feat(gazelle): support generic manifest lockfiles (#4117)
Adds a format-agnostic lockfiles argument to gazelle_python_manifest and
deprecates requirements.
This lets callers use uv.lock or any dependency lockfile as the manifest
integrity input. The existing build-file-generation example now
exercises lockfiles.
Fixes #4111.
The macro rejects specifying both arguments so existing requirements
users can migrate without ambiguity.
diff --git a/examples/build_file_generation/BUILD.bazel b/examples/build_file_generation/BUILD.bazel
index 27f0145..bfff6f3 100644
--- a/examples/build_file_generation/BUILD.bazel
+++ b/examples/build_file_generation/BUILD.bazel
@@ -40,11 +40,11 @@
# the manifest doesn't need to be updated
gazelle_python_manifest(
name = "gazelle_python_manifest",
- modules_mapping = ":modules_map",
- pip_repository_name = "pip",
# NOTE: We can pass a list just like in `bzlmod_build_file_generation` example
# but we keep a single target here for regression testing.
- requirements = "//:requirements_lock.txt",
+ lockfiles = "//:requirements_lock.txt",
+ modules_mapping = ":modules_map",
+ pip_repository_name = "pip",
)
# Our gazelle target points to the python gazelle binary.
diff --git a/gazelle/docs/installation_and_usage.md b/gazelle/docs/installation_and_usage.md
index c3a0043..16bccde 100644
--- a/gazelle/docs/installation_and_usage.md
+++ b/gazelle/docs/installation_and_usage.md
@@ -113,16 +113,21 @@
# python libraries are loaded in BUILD files.
pip_repository_name = "pip",
- # This should point to wherever we declare our python dependencies
- # (the same as what we passed to the modules_mapping rule in WORKSPACE)
+ # This should point to the lockfile for our Python dependencies.
# This argument is optional. If provided, the `.test` target is very
# fast because it just has to check an integrity field. If not provided,
# the integrity field is not added to the manifest which can help avoid
# merge conflicts in large repos.
- requirements = "//:requirements_lock.txt",
+ # `lockfiles` accepts one file or a list, including a `uv.lock` file.
+ lockfiles = "//:uv.lock",
)
```
+:::{versionchanged} VERSION_NEXT_FEATURE
+The `requirements` argument is deprecated in favor of `lockfiles`, which can
+refer to any dependency lockfile, including `uv.lock`.
+:::
+
Finally, you create a target that you'll invoke to run the Gazelle tool
with the `rules_python` extension included. This typically goes in your root
`/BUILD.bazel` file:
diff --git a/gazelle/manifest/defs.bzl b/gazelle/manifest/defs.bzl
index 71a4d57..c61618f 100644
--- a/gazelle/manifest/defs.bzl
+++ b/gazelle/manifest/defs.bzl
@@ -23,21 +23,28 @@
def gazelle_python_manifest(
name,
modules_mapping,
- requirements = [],
+ requirements = None,
+ lockfiles = [],
pip_repository_name = "",
pip_deps_repository_name = "",
manifest = ":gazelle_python.yaml",
**kwargs):
"""A macro for defining the updating and testing targets for the Gazelle manifest file.
+ :::{versionchanged} VERSION_NEXT_FEATURE
+ The `requirements` argument is deprecated in favor of `lockfiles`, which
+ can refer to any dependency lockfile, including `uv.lock`.
+ :::
+
Args:
name: the name used as a base for the targets.
modules_mapping: the target for the generated modules_mapping.json file.
- requirements: the target for the requirements.txt file or a list of
- requirements files that will be concatenated before passing on to
- the manifest generator. If unset, no integrity field is added to the
- manifest, meaning testing it is just as expensive as generating it,
- but modifying it is much less likely to result in a merge conflict.
+ requirements: deprecated. Use lockfiles instead.
+ lockfiles: the target for a lockfile or a list of lockfiles that will be
+ concatenated before passing on to the manifest generator. If unset,
+ no integrity field is added to the manifest, meaning testing it is
+ just as expensive as generating it, but modifying it is much less
+ likely to result in a merge conflict.
pip_repository_name: the name of the pip_install or pip_repository target.
pip_deps_repository_name: deprecated - the old {bzl:obj}`pip_parse` target name.
manifest: the Gazelle manifest file.
@@ -57,6 +64,20 @@
# This is a temporary check while pip_deps_repository_name exists as deprecated.
fail("pip_repository_name must be set in //{}:{}".format(native.package_name(), name))
+ if requirements != None:
+ if lockfiles:
+ fail("only one of requirements or lockfiles may be set in //{}:{}".format(
+ native.package_name(),
+ name,
+ ))
+
+ # buildifier: disable=print
+ print("DEPRECATED requirements in //{}:{}. Please use lockfiles instead.".format(
+ native.package_name(),
+ name,
+ ))
+ lockfiles = requirements
+
test_target = "{}.test".format(name)
update_target = "{}.update".format(name)
update_target_label = "//{}:{}".format(native.package_name(), update_target)
@@ -66,20 +87,19 @@
manifest_generator = Label("//manifest/generate:generate")
manifest_generator_hash = Label("//manifest/generate:generate_lib_sources_hash")
- if requirements and type(requirements) == "list":
- # This runs if requirements is a list or is unset (default value is empty list)
+ if lockfiles and type(lockfiles) == "list":
native.genrule(
- name = name + "_requirements_gen",
- srcs = sorted(requirements),
- outs = [name + "_requirements.txt"],
+ name = name + "_lockfiles_gen",
+ srcs = sorted(lockfiles),
+ outs = [name + "_lockfiles.txt"],
cmd_bash = "cat $(SRCS) > $@",
cmd_bat = "type $(SRCS) > $@",
)
- requirements = name + "_requirements_gen"
+ lockfiles = name + "_lockfiles_gen"
update_args = [
"--manifest-generator-hash=$(execpath {})".format(manifest_generator_hash),
- "--requirements=$(execpath {})".format(requirements) if requirements else "--requirements=",
+ "--requirements=$(execpath {})".format(lockfiles) if lockfiles else "--requirements=",
"--pip-repository-name={}".format(pip_repository_name),
"--modules-mapping=$(execpath {})".format(modules_mapping),
"--output=$(execpath {})".format(generated_manifest),
@@ -94,7 +114,7 @@
srcs = [
modules_mapping,
manifest_generator_hash,
- ] + ([requirements] if requirements else []),
+ ] + ([lockfiles] if lockfiles else []),
tags = ["manual"],
)
@@ -114,12 +134,12 @@
**{k: v for k, v in kwargs.items() if k != "tags"}
)
- if requirements:
+ if lockfiles:
attrs = {
"env": {
"_TEST_MANIFEST": "$(rlocationpath {})".format(manifest),
"_TEST_MANIFEST_GENERATOR_HASH": "$(rlocationpath {})".format(manifest_generator_hash),
- "_TEST_REQUIREMENTS": "$(rlocationpath {})".format(requirements),
+ "_TEST_REQUIREMENTS": "$(rlocationpath {})".format(lockfiles),
},
"size": "small",
}
@@ -128,7 +148,7 @@
srcs = [Label("//manifest/test:test.go")],
data = [
manifest,
- requirements,
+ lockfiles,
manifest_generator_hash,
],
rundir = ".",
diff --git a/news/4111.changed.md b/news/4111.changed.md
new file mode 100644
index 0000000..bde84bc
--- /dev/null
+++ b/news/4111.changed.md
@@ -0,0 +1,2 @@
+(gazelle) Deprecate the `requirements` argument of `gazelle_python_manifest`
+in favor of the format-agnostic `lockfiles` argument, which supports `uv.lock`.