Skip to content

Commit 1bc622f

Browse files
committed
Merge branch 'main' into aignas.chore.remove-root-error-and-chmod
2 parents f1c91a3 + 7ea4706 commit 1bc622f

26 files changed

+684
-244
lines changed

CHANGELOG.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,23 @@ END_UNRELEASED_TEMPLATE
5555

5656
{#v0-0-0-removed}
5757
### Removed
58+
* (toolchain) Remove all of the python 3.8 toolchain support out of the box. Users need
59+
to pass the `TOOL_VERSIONS` that include 3.8 toolchains or use the `bzlmod` APIs to add
60+
them back. This means any hub `pip.parse` calls that target `3.8` will be ignored from
61+
now on. ([#2704](https://github.com/bazel-contrib/rules_python/issues/2704))
62+
* (toolchain) Remove all of the python 3.9 toolchain versions except for the `3.9.25`.
63+
This version has reached EOL and will no longer receive any security fixes, please update to
64+
`3.10` or above. ([#2704](https://github.com/bazel-contrib/rules_python/issues/2704))
5865
* (toolchain) `ignore_root_user_error` has now been flipped to be always enabled and
5966
the `chmod` of the python toolchain directories have been removed. From now on `rules_python`
6067
always adds the `pyc` files to the glob excludes and in order to avoid any problems when using
6168
the toolchains in the repository phase, ensure that you pass `-B` to the python interpreter.
6269
([#2016](https://github.com/bazel-contrib/rules_python/issues/2016))
63-
* (toolchain) Remove all of the python 3.9 toolchain versions except for the `3.9.25`.
64-
This version has reached EOL and will no longer receive any security fixes, please update to
65-
`3.10` or above.
6670

6771
{#v0-0-0-changed}
6872
### Changed
6973
* (toolchains) Use toolchains from the [20251031] release.
74+
* (gazelle) Internally split modules mapping generation to be per-wheel for concurrency and caching.
7075

7176
{#v0-0-0-fixed}
7277
### Fixed
@@ -78,8 +83,15 @@ END_UNRELEASED_TEMPLATE
7883
([#3085](https://github.com/bazel-contrib/rules_python/issues/3085)).
7984
* (toolchains) local toolchains now tell the `sys.abiflags` value of the
8085
underlying runtime.
86+
* (toolchains) various local toolchain fixes: add abi3 header targets,
87+
fixes to linking, Windows DLL detection, and defines for free threaded
88+
runtimes.
89+
* (toolchains) The `python_headers` target is now compatible with
90+
layering_check.
8191
* (performance) 90% reduction in py_binary/py_test analysis phase cost.
8292
([#3381](https://github.com/bazel-contrib/rules_python/pull/3381)).
93+
* (gazelle) Fix `gazelle_python_manifest.test` so that it accesses manifest files via `runfile` path handling rather than directly ([#3397](https://github.com/bazel-contrib/rules_python/issues/3397)).
94+
8395

8496
{#v0-0-0-added}
8597
### Added

docs/howto/common-deps-with-multiple-pypi-versions.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ rules_python_config.add_transition_setting(
4343

4444
# File: BUILD.bazel
4545

46-
```bzl
47-
4846
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
4947

5048
string_flag(

examples/bzlmod/.bazelrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Starting with Bazel 8, Windows requires this flag in order
2+
# for symlinks to work properly (namely, so that sh_test with
3+
# py_binary as a data dependency gets symlinks that are executable)
4+
startup --windows_enable_symlinks
5+
16
common --enable_bzlmod
27
common --lockfile_mode=update
38

gazelle/manifest/defs.bzl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ def gazelle_python_manifest(
117117
if requirements:
118118
attrs = {
119119
"env": {
120-
"_TEST_MANIFEST": "$(rootpath {})".format(manifest),
120+
"_TEST_MANIFEST": "$(rlocationpath {})".format(manifest),
121121
"_TEST_MANIFEST_GENERATOR_HASH": "$(rlocationpath {})".format(manifest_generator_hash),
122-
"_TEST_REQUIREMENTS": "$(rootpath {})".format(requirements),
122+
"_TEST_REQUIREMENTS": "$(rlocationpath {})".format(requirements),
123123
},
124124
"size": "small",
125125
}

gazelle/manifest/test/test.go

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,50 @@ import (
2626
"path/filepath"
2727
"testing"
2828

29-
"github.com/bazelbuild/rules_go/go/runfiles"
3029
"github.com/bazel-contrib/rules_python/gazelle/manifest"
30+
"github.com/bazelbuild/rules_go/go/runfiles"
3131
)
3232

33-
func TestGazelleManifestIsUpdated(t *testing.T) {
34-
requirementsPath := os.Getenv("_TEST_REQUIREMENTS")
35-
if requirementsPath == "" {
36-
t.Fatal("_TEST_REQUIREMENTS must be set")
33+
// getResolvedRunfile resolves an environment variable to a runfiles path.
34+
// It handles getting the env var, checking it's set, and resolving it through
35+
// the runfiles mechanism, providing detailed error messages if anything fails.
36+
func getResolvedRunfile(t *testing.T, envVar string) string {
37+
t.Helper()
38+
path := os.Getenv(envVar)
39+
if path == "" {
40+
t.Fatalf("%s must be set", envVar)
3741
}
38-
39-
manifestPath := os.Getenv("_TEST_MANIFEST")
40-
if manifestPath == "" {
41-
t.Fatal("_TEST_MANIFEST must be set")
42+
resolvedPath, err := runfiles.Rlocation(path)
43+
if err != nil {
44+
t.Fatalf("failed to resolve runfiles path for %s (%q): %v", envVar, path, err)
4245
}
46+
return resolvedPath
47+
}
48+
49+
func TestGazelleManifestIsUpdated(t *testing.T) {
50+
requirementsPathResolved := getResolvedRunfile(t, "_TEST_REQUIREMENTS")
51+
manifestPathResolved := getResolvedRunfile(t, "_TEST_MANIFEST")
4352

4453
manifestFile := new(manifest.File)
45-
if err := manifestFile.Decode(manifestPath); err != nil {
54+
if err := manifestFile.Decode(manifestPathResolved); err != nil {
4655
t.Fatalf("decoding manifest file: %v", err)
4756
}
4857

4958
if manifestFile.Integrity == "" {
5059
t.Fatal("failed to find the Gazelle manifest file integrity")
5160
}
5261

53-
manifestGeneratorHashPath, err := runfiles.Rlocation(
54-
os.Getenv("_TEST_MANIFEST_GENERATOR_HASH"))
55-
if err != nil {
56-
t.Fatalf("failed to resolve runfiles path of manifest: %v", err)
57-
}
62+
manifestGeneratorHashPath := getResolvedRunfile(t, "_TEST_MANIFEST_GENERATOR_HASH")
5863

5964
manifestGeneratorHash, err := os.Open(manifestGeneratorHashPath)
6065
if err != nil {
6166
t.Fatalf("opening %q: %v", manifestGeneratorHashPath, err)
6267
}
6368
defer manifestGeneratorHash.Close()
6469

65-
requirements, err := os.Open(requirementsPath)
70+
requirements, err := os.Open(requirementsPathResolved)
6671
if err != nil {
67-
t.Fatalf("opening %q: %v", requirementsPath, err)
72+
t.Fatalf("opening %q: %v", requirementsPathResolved, err)
6873
}
6974
defer requirements.Close()
7075

@@ -73,9 +78,9 @@ func TestGazelleManifestIsUpdated(t *testing.T) {
7378
t.Fatalf("verifying integrity: %v", err)
7479
}
7580
if !valid {
76-
manifestRealpath, err := filepath.EvalSymlinks(manifestPath)
81+
manifestRealpath, err := filepath.EvalSymlinks(manifestPathResolved)
7782
if err != nil {
78-
t.Fatalf("evaluating symlink %q: %v", manifestPath, err)
83+
t.Fatalf("evaluating symlink %q: %v", manifestPathResolved, err)
7984
}
8085
t.Errorf(
8186
"%q is out-of-date. Follow the update instructions in that file to resolve this",

gazelle/modules_mapping/BUILD.bazel

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ py_binary(
99
visibility = ["//visibility:public"],
1010
)
1111

12+
py_binary(
13+
name = "merger",
14+
srcs = ["merger.py"],
15+
visibility = ["//visibility:public"],
16+
)
17+
1218
copy_file(
1319
name = "pytest_wheel",
1420
src = "@pytest//file",
@@ -33,6 +39,18 @@ py_test(
3339
deps = [":generator"],
3440
)
3541

42+
py_test(
43+
name = "test_merger",
44+
srcs = ["test_merger.py"],
45+
data = [
46+
"django_types_wheel",
47+
"pytest_wheel",
48+
],
49+
imports = ["."],
50+
main = "test_merger.py",
51+
deps = [":merger"],
52+
)
53+
3654
filegroup(
3755
name = "distribution",
3856
srcs = glob(["**"]),

gazelle/modules_mapping/def.bzl

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,39 @@ def _modules_mapping_impl(ctx):
3030
transitive = [dep[DefaultInfo].files for dep in ctx.attr.wheels] + [dep[DefaultInfo].data_runfiles.files for dep in ctx.attr.wheels],
3131
)
3232

33-
args = ctx.actions.args()
33+
# Run the generator once per-wheel (to leverage caching)
34+
per_wheel_outputs = []
35+
for idx, whl in enumerate(all_wheels.to_list()):
36+
wheel_modules_mapping = ctx.actions.declare_file("{}.{}".format(modules_mapping.short_path, idx))
37+
args = ctx.actions.args()
38+
args.add("--output_file", wheel_modules_mapping.path)
39+
if ctx.attr.include_stub_packages:
40+
args.add("--include_stub_packages")
41+
args.add_all("--exclude_patterns", ctx.attr.exclude_patterns)
42+
args.add("--wheel", whl.path)
3443

35-
# Spill parameters to a file prefixed with '@'. Note, the '@' prefix is the same
36-
# prefix as used in the `generator.py` in `fromfile_prefix_chars` attribute.
37-
args.use_param_file(param_file_arg = "@%s")
38-
args.set_param_file_format(format = "multiline")
39-
if ctx.attr.include_stub_packages:
40-
args.add("--include_stub_packages")
41-
args.add("--output_file", modules_mapping)
42-
args.add_all("--exclude_patterns", ctx.attr.exclude_patterns)
43-
args.add_all("--wheels", all_wheels)
44+
ctx.actions.run(
45+
inputs = [whl],
46+
outputs = [wheel_modules_mapping],
47+
executable = ctx.executable._generator,
48+
arguments = [args],
49+
use_default_shell_env = False,
50+
)
51+
per_wheel_outputs.append(wheel_modules_mapping)
52+
53+
# Then merge the individual JSONs together
54+
merge_args = ctx.actions.args()
55+
merge_args.add("--output", modules_mapping.path)
56+
merge_args.add_all("--inputs", [f.path for f in per_wheel_outputs])
4457

4558
ctx.actions.run(
46-
inputs = all_wheels,
59+
inputs = per_wheel_outputs,
4760
outputs = [modules_mapping],
48-
executable = ctx.executable._generator,
49-
arguments = [args],
61+
executable = ctx.executable._merger,
62+
arguments = [merge_args],
5063
use_default_shell_env = False,
5164
)
65+
5266
return [DefaultInfo(files = depset([modules_mapping]))]
5367

5468
modules_mapping = rule(
@@ -79,6 +93,11 @@ modules_mapping = rule(
7993
default = "//modules_mapping:generator",
8094
executable = True,
8195
),
96+
"_merger": attr.label(
97+
cfg = "exec",
98+
default = "//modules_mapping:merger",
99+
executable = True,
100+
),
82101
},
83102
doc = "Creates a modules_mapping.json file for mapping module names to wheel distribution names.",
84103
)

gazelle/modules_mapping/generator.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,28 @@ def module_for_path(self, path, whl):
9696
ext = "".join(pathlib.Path(root).suffixes)
9797
module = root[: -len(ext)].replace("/", ".")
9898
if not self.is_excluded(module):
99-
if not self.is_excluded(module):
100-
self.mapping[module] = wheel_name
99+
self.mapping[module] = wheel_name
101100

102101
def is_excluded(self, module):
103102
for pattern in self.excluded_patterns:
104103
if pattern.search(module):
105104
return True
106105
return False
107106

108-
# run is the entrypoint for the generator.
109-
def run(self, wheels):
110-
for whl in wheels:
111-
try:
112-
self.dig_wheel(whl)
113-
except AssertionError as error:
114-
print(error, file=self.stderr)
115-
return 1
107+
def run(self, wheel: pathlib.Path) -> int:
108+
"""
109+
Entrypoint for the generator.
110+
111+
Args:
112+
wheel: The path to the wheel file (`.whl`)
113+
Returns:
114+
Exit code (for `sys.exit`)
115+
"""
116+
try:
117+
self.dig_wheel(wheel)
118+
except AssertionError as error:
119+
print(error, file=self.stderr)
120+
return 1
116121
self.simplify()
117122
mapping_json = json.dumps(self.mapping)
118123
with open(self.output_file, "w") as f:
@@ -152,16 +157,13 @@ def data_has_purelib_or_platlib(path):
152157
parser = argparse.ArgumentParser(
153158
prog="generator",
154159
description="Generates the modules mapping used by the Gazelle manifest.",
155-
# Automatically read parameters from a file. Note, the '@' is the same prefix
156-
# as set in the 'args.use_param_file' in the bazel rule.
157-
fromfile_prefix_chars="@",
158160
)
159161
parser.add_argument("--output_file", type=str)
160162
parser.add_argument("--include_stub_packages", action="store_true")
161163
parser.add_argument("--exclude_patterns", nargs="+", default=[])
162-
parser.add_argument("--wheels", nargs="+", default=[])
164+
parser.add_argument("--wheel", type=pathlib.Path)
163165
args = parser.parse_args()
164166
generator = Generator(
165167
sys.stderr, args.output_file, args.exclude_patterns, args.include_stub_packages
166168
)
167-
sys.exit(generator.run(args.wheels))
169+
sys.exit(generator.run(args.wheel))

gazelle/modules_mapping/merger.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env python3
2+
"""Merges multiple modules_mapping.json files into a single file."""
3+
4+
import argparse
5+
import json
6+
from pathlib import Path
7+
8+
9+
def merge_modules_mappings(input_files: list[Path], output_file: Path) -> None:
10+
"""Merge multiple modules_mapping.json files into one.
11+
12+
Args:
13+
input_files: List of paths to input JSON files to merge
14+
output_file: Path where the merged output should be written
15+
"""
16+
merged_mapping = {}
17+
for input_file in input_files:
18+
mapping = json.loads(input_file.read_text())
19+
# Merge the mappings, with later files overwriting earlier ones
20+
# if there are conflicts
21+
merged_mapping.update(mapping)
22+
23+
output_file.write_text(json.dumps(merged_mapping))
24+
25+
26+
if __name__ == "__main__":
27+
parser = argparse.ArgumentParser(
28+
description="Merge multiple modules_mapping.json files"
29+
)
30+
parser.add_argument(
31+
"--output",
32+
required=True,
33+
type=Path,
34+
help="Output file path for merged mapping",
35+
)
36+
parser.add_argument(
37+
"--inputs",
38+
required=True,
39+
nargs="+",
40+
type=Path,
41+
help="Input JSON files to merge",
42+
)
43+
44+
args = parser.parse_args()
45+
merge_modules_mappings(args.inputs, args.output)

0 commit comments

Comments
 (0)