Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/uv.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,17 @@ load("@aspect_rules_py//uv:defs.bzl", "gazelle_python_manifest")
gazelle_python_manifest(
name = "gazelle_python_manifest",
hub = "pypi",
include_stub_packages = True,
venvs = ["default"],
)
```

**Parameters:**

- `hub` — The name of your uv hub (must match `uv.declare_hub(hub_name = ...)`).
- `include_stub_packages` — Whether to index conventional stub distributions such as
`types-requests` and `asyncpg-stubs`. The Gazelle Python extension then adds matching
stub dependencies automatically. Defaults to `False`.
- `venvs` — List of dependency group names whose wheels should be indexed. Module mappings
from all listed dependency groups are merged into a single manifest.

Expand Down
1 change: 1 addition & 0 deletions e2e/cases/uv-gazelle-778/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ load("@aspect_rules_py//uv:defs.bzl", "gazelle_python_manifest")
gazelle_python_manifest(
name = "gazelle_python_manifest",
hub = "pypi_uv_deps_650",
include_stub_packages = True,
# Note that we _merge_ the package mappings from several configurations.
venvs = [
"airflow",
Expand Down
1 change: 1 addition & 0 deletions e2e/cases/uv-gazelle-778/gazelle_python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ manifest:
text_unidecode: text_unidecode
tools: cron_descriptor
typer: typer
types_requests: types_requests
typing_extensions: typing_extensions
typing_inspection: typing_inspection
tzdata: tzdata
Expand Down
15 changes: 13 additions & 2 deletions uv/private/gazelle_manifest/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def _modules_mapping_impl(ctx):
args_file.path,
"--output",
out.path,
],
] + (["--include_stub_packages"] if ctx.attr.include_stub_packages else []),
inputs = [
args_file,
] + whl_files,
Expand All @@ -59,6 +59,7 @@ _modules_mapping = rule(
attrs = {
"wheels": attr.label_list(providers = [[DefaultInfo]]),
"hub": attr.string(),
"include_stub_packages": attr.bool(),
"_generator": attr.label(
default = Label(":generator"),
executable = True,
Expand All @@ -69,7 +70,16 @@ _modules_mapping = rule(

update = Label(":update.sh")

def gazelle_python_manifest(name, hub, venvs = []):
def gazelle_python_manifest(name, hub, venvs = [], include_stub_packages = False):
"""Generates a Gazelle Python manifest from uv-managed wheels.

Args:
name: Name of the generated manifest target.
hub: Name of the uv hub containing the wheels.
venvs: Dependency groups whose wheels should be indexed.
include_stub_packages: Whether conventional stub distributions should be
indexed for Gazelle's automatic stub dependency resolution.
"""
file = "gazelle_python.yaml"
hub = hub.lstrip("@")

Expand Down Expand Up @@ -98,6 +108,7 @@ def gazelle_python_manifest(name, hub, venvs = []):
name = name,
wheels = whls,
hub = hub,
include_stub_packages = include_stub_packages,
)

dest = native.package_name()
Expand Down
19 changes: 19 additions & 0 deletions uv/private/gazelle_manifest/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ def normalize_name(name: str) -> str:
])


def is_stub_package(name: str) -> bool:
"""Return whether a normalized distribution name follows a stub-package convention."""
return (
name.endswith(("_stubs", "_types"))
or name.startswith(("types_", "stubs_"))
)


def extract_package_name(whl_path: Path) -> Optional[str]:
"""
Opens a .whl file, finds the METADATA file in .dist-info/, and extracts
Expand Down Expand Up @@ -281,6 +289,12 @@ def main() -> None:
'--hub_name',
)

parser.add_argument(
'--include_stub_packages',
action='store_true',
help="Include conventional stub distributions for Gazelle's automatic stub dependency resolution.",
)

args = parser.parse_args()

# Read wheel paths
Expand Down Expand Up @@ -317,6 +331,11 @@ def main() -> None:
if not package_name:
continue

# The Gazelle Python resolver looks up these synthetic module names
# after resolving the corresponding runtime distribution.
if args.include_stub_packages and is_stub_package(package_name):
all_module_package_pairs.append((package_name, package_name))

# Identify importable modules for this package
modules = identify_modules(whl_path, package_name)

Expand Down
14 changes: 13 additions & 1 deletion uv/private/gazelle_manifest/test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
#!/usr/bin/env python3

from generate import get_importable_module_name, find_unique_shallowest_prefixes
from generate import find_unique_shallowest_prefixes, get_importable_module_name, is_stub_package


def test_stub_package_names() -> None:
# Given: normalized distribution names covering supported stub conventions.
package_names = ["asyncpg_stubs", "types_requests", "foo_types", "stubs_foo", "requests"]

# When: each name is classified.
classifications = [is_stub_package(name) for name in package_names]

# Then: conventional stub names are recognized without matching runtime packages.
assert classifications == [True, True, True, True, False]


def test_parse_names() -> None:
assert get_importable_module_name("foo.py") == "foo"
Expand Down