Skip to content

Commit 234bf5f

Browse files
committed
Merge branch 'fix.conflict.merging.files' into refactor.optimize.venv.namespace.packages
2 parents 01196b4 + 8a752a0 commit 234bf5f

2 files changed

Lines changed: 95 additions & 63 deletions

File tree

python/private/venv_runfiles.bzl

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,10 @@ def _group_venv_path_entries(entries):
171171
current_group = None
172172
current_group_prefix = None
173173
for entry in entries:
174-
prefix = entry.venv_path
175-
anchored_prefix = prefix + "/"
174+
# NOTE: When a file is being directly linked, the anchored prefix can look
175+
# odd, e.g. "foo/__init__.py/". This is OK; it's just used to prevent
176+
# incorrect prefix substring matching.
177+
anchored_prefix = entry.venv_path + "/"
176178
if (current_group_prefix == None or
177179
not anchored_prefix.startswith(current_group_prefix)):
178180
current_group_prefix = anchored_prefix
@@ -205,26 +207,41 @@ def _merge_venv_path_group(ctx, group, keep_map):
205207
# be symlinked directly, not the directory containing them, due to
206208
# dynamic linker symlink resolution semantics on Linux.
207209
for entry in group:
208-
prefix = entry.venv_path
210+
root_venv_path = entry.venv_path
211+
anchored_link_to_path = entry.link_to_path + "/"
209212
for file in entry.files.to_list():
210-
# Compute the file-specific venv path. i.e. the relative
211-
# path of the file under entry.venv_path, joined with
212-
# entry.venv_path
213213
rf_root_path = runfiles_root_path(ctx, file.short_path)
214-
if not rf_root_path.startswith(entry.link_to_path):
215-
# This generally shouldn't occur in practice, but just
216-
# in case, skip them, for lack of a better option.
217-
continue
218-
venv_path = "{}/{}".format(
219-
prefix,
220-
rf_root_path.removeprefix(entry.link_to_path + "/"),
221-
)
222214

223-
# For lack of a better option, first added wins. We happen to
224-
# go in top-down prefix order, so the highest level namespace
225-
# package typically wins.
226-
if venv_path not in keep_map:
227-
keep_map[venv_path] = file
215+
# It's a file (or directory) being directly linked and
216+
# must be directly linked.
217+
if rf_root_path == entry.link_to_path:
218+
# For lack of a better option, first added wins.
219+
if entry.venv_path not in keep_map:
220+
keep_map[entry.venv_path] = file
221+
222+
# Skip anything remaining: anything left is either
223+
# the same path (first set wins), a suffix (violates
224+
# preconditions and can't link anyways), or not under
225+
# the prefix (violates preconditions).
226+
break
227+
else:
228+
# Compute the file-specific venv path. i.e. the relative
229+
# path of the file under entry.venv_path, joined with
230+
# entry.venv_path
231+
head, match, rel_venv_path = rf_root_path.partition(anchored_link_to_path)
232+
if not match or head:
233+
# If link_to_path didn't match, then obviously skip.
234+
# If head is non-empty, it means link_to_path wasn't
235+
# found at the start
236+
# This shouldn't occur in practice, but guard against it
237+
# just in case
238+
continue
239+
240+
venv_path = paths.join(root_venv_path, rel_venv_path)
241+
242+
# For lack of a better option, first added wins.
243+
if venv_path not in keep_map:
244+
keep_map[venv_path] = file
228245

229246
def _is_importable_name(name):
230247
# Requires Bazel 8+

tests/venv_site_packages_libs/app_files_building/app_files_building_tests.bzl

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
""
22

3-
load("@bazel_skylib//lib:paths.bzl", "paths")
43
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
54
load("@rules_testing//lib:test_suite.bzl", "test_suite")
65
load("//python/private:py_info.bzl", "VenvSymlinkEntry", "VenvSymlinkKind") # buildifier: disable=bzl-visibility
@@ -58,34 +57,16 @@ def _venv_symlinks_from_entries(entries):
5857
))
5958
return sorted(result, key = lambda e: (e.link_to_path, e.venv_path))
6059

61-
def _entry(venv_path, link_to_path, files = [], **kwargs):
60+
def _entry(venv_path, link_to_path, files, **kwargs):
6261
kwargs.setdefault("kind", VenvSymlinkKind.LIB)
6362
kwargs.setdefault("package", None)
6463
kwargs.setdefault("version", None)
65-
66-
def short_pathify(path):
67-
path = paths.join(link_to_path, path)
68-
69-
# In tests, `../` is used to step out of the link_to_path scope.
70-
path = paths.normalize(path)
71-
72-
# Treat paths starting with "+" as external references. This matches
73-
# how bzlmod names things.
74-
if link_to_path.startswith("+"):
75-
# File.short_path to external repos have `../` prefixed
76-
path = paths.join("../", path)
77-
else:
78-
# File.short_path in main repo is main-repo relative
79-
_, _, path = path.partition("/")
80-
return path
64+
kwargs.setdefault("link_to_file", None)
8165

8266
return VenvSymlinkEntry(
8367
venv_path = venv_path,
8468
link_to_path = link_to_path,
85-
files = depset([
86-
_file(short_pathify(f))
87-
for f in files
88-
]),
69+
files = depset(files),
8970
**kwargs
9071
)
9172

@@ -100,15 +81,34 @@ _tests.append(_test_conflict_merging)
10081

10182
def _test_conflict_merging_impl(env, _):
10283
entries = [
103-
_entry("a", "+pypi_a/site-packages/a", ["a.txt"]),
104-
_entry("a-1.0.dist-info", "+pypi_a/site-packages/a-1.0.dist-info", ["METADATA"]),
105-
_entry("a/b", "+pypi_a_b/site-packages/a/b", ["b.txt"]),
106-
_entry("x", "_main/src/x", ["x.txt"]),
107-
_entry("x/p", "_main/src-dev/x/p", ["p.txt"]),
108-
_entry("duplicate", "+dupe_a/site-packages/duplicate", ["d.py"]),
109-
# This entry also provides a/x.py, but since the "a" entry is shorter
110-
# and comes first, its version of x.py should win.
111-
_entry("duplicate", "+dupe_b/site-packages/duplicate", ["d.py"]),
84+
_entry("a", "+pypi_a/site-packages/a", [
85+
_file("../+pypi_a/site-packages/a/a.txt"),
86+
]),
87+
_entry("a-1.0.dist-info", "+pypi_a/site-packages/a-1.0.dist-info", [
88+
_file("../+pypi_a/site-packages/a-1.0.dist-info/METADATA"),
89+
]),
90+
_entry("a/b", "+pypi_a_b/site-packages/a/b", [
91+
_file("../+pypi_a_b/site-packages/a/b/b.txt"),
92+
]),
93+
_entry("x", "_main/src/x", [
94+
_file("src/x/x.txt"),
95+
]),
96+
_entry("x/p", "_main/src-dev/x/p", [
97+
_file("src-dev/x/p/p.txt"),
98+
]),
99+
_entry("duplicate", "+dupe_a/site-packages/duplicate", [
100+
_file("../+dupe_a/site-packages/duplicate/d.py"),
101+
]),
102+
_entry("duplicate", "+dupe_b/site-packages/duplicate", [
103+
_file("../+dupe_b/site-packages/duplicate/d.py"),
104+
]),
105+
# Case: two distributions provide the same file (instead of directory)
106+
_entry("ff/fmod.py", "+ff_a/site-packages/ff/fmod.py", [
107+
_file("../+ff_a/site-packages/ff/fmod.py"),
108+
]),
109+
_entry("ff/fmod.py", "+ff_b/site-packages/ff/fmod.py", [
110+
_file("../+ff_b/site-packages/ff/fmod.py"),
111+
]),
112112
]
113113

114114
actual, conflicts = build_link_map(_ctx(), entries, return_conflicts = True)
@@ -117,6 +117,7 @@ def _test_conflict_merging_impl(env, _):
117117
"a/a.txt": _file("../+pypi_a/site-packages/a/a.txt"),
118118
"a/b/b.txt": _file("../+pypi_a_b/site-packages/a/b/b.txt"),
119119
"duplicate/d.py": _file("../+dupe_a/site-packages/duplicate/d.py"),
120+
"ff/fmod.py": _file("../+ff_a/site-packages/ff/fmod.py"),
120121
"x/p/p.txt": _file("src-dev/x/p/p.txt"),
121122
"x/x.txt": _file("src/x/x.txt"),
122123
}
@@ -340,8 +341,12 @@ _tests.append(_test_package_version_filtering)
340341

341342
def _test_package_version_filtering_impl(env, _):
342343
entries = [
343-
_entry("foo", "+pypi_v1/site-packages/foo", ["foo.txt"], package = "foo", version = "1.0"),
344-
_entry("foo", "+pypi_v2/site-packages/foo", ["bar.txt"], package = "foo", version = "2.0"),
344+
_entry("foo", "+pypi_v1/site-packages/foo", [
345+
_file("../+pypi_v1/site-packages/foo/foo.txt"),
346+
], package = "foo", version = "1.0"),
347+
_entry("foo", "+pypi_v2/site-packages/foo", [
348+
_file("../+pypi_v2/site-packages/foo/bar.txt"),
349+
], package = "foo", version = "2.0"),
345350
]
346351

347352
actual = build_link_map(_ctx(), entries)
@@ -366,15 +371,15 @@ def _test_malformed_entry_impl(env, _):
366371
"a",
367372
"+pypi_a/site-packages/a",
368373
# This file is outside the link_to_path, so it should be ignored.
369-
["../outside.txt"],
374+
[_file("../+pypi_a/site-packages/outside.txt")],
370375
),
371376
# A second, conflicting, entry is added to force merging of the known
372-
# files. Without this, there's no conflict, so files is never
377+
# files. Without this, there\'s no conflict, so files is never
373378
# considered.
374379
_entry(
375380
"a",
376381
"+pypi_b/site-packages/a",
377-
["../outside.txt"],
382+
[_file("../+pypi_b/site-packages/outside.txt")],
378383
),
379384
]
380385

@@ -394,11 +399,21 @@ _tests.append(_test_complex_namespace_packages)
394399

395400
def _test_complex_namespace_packages_impl(env, _):
396401
entries = [
397-
_entry("a/b", "+pypi_a_b/site-packages/a/b", ["b.txt"]),
398-
_entry("a/c", "+pypi_a_c/site-packages/a/c", ["c.txt"]),
399-
_entry("x/y/z", "+pypi_x_y_z/site-packages/x/y/z", ["z.txt"]),
400-
_entry("foo", "+pypi_foo/site-packages/foo", ["foo.txt"]),
401-
_entry("foobar", "+pypi_foobar/site-packages/foobar", ["foobar.txt"]),
402+
_entry("a/b", "+pypi_a_b/site-packages/a/b", [
403+
_file("../+pypi_a_b/site-packages/a/b/b.txt"),
404+
]),
405+
_entry("a/c", "+pypi_a_c/site-packages/a/c", [
406+
_file("../+pypi_a_c/site-packages/a/cc.txt"),
407+
]),
408+
_entry("x/y/z", "+pypi_x_y_z/site-packages/x/y/z", [
409+
_file("../+pypi_x_y_z/site-packages/x/y/z/z.txt"),
410+
]),
411+
_entry("foo", "+pypi_foo/site-packages/foo", [
412+
_file("../+pypi_foo/site-packages/foo/foo.txt"),
413+
]),
414+
_entry("foobar", "+pypi_foobar/site-packages/foobar", [
415+
_file("../+pypi_foobar/site-packages/foobar/foobar.txt"),
416+
]),
402417
]
403418

404419
actual = build_link_map(_ctx(), entries)
@@ -446,19 +461,19 @@ def _test_multiple_venv_symlink_kinds_impl(env, _):
446461
_entry(
447462
"libfile",
448463
"+pypi_lib/site-packages/libfile",
449-
["lib.txt"],
464+
[_file("../+pypi_lib/site-packages/libfile/lib.txt")],
450465
kind = VenvSymlinkKind.LIB,
451466
),
452467
_entry(
453468
"binfile",
454469
"+pypi_bin/bin/binfile",
455-
["bin.txt"],
470+
[_file("../+pypi_bin/bin/binfile/bin.txt")],
456471
kind = VenvSymlinkKind.BIN,
457472
),
458473
_entry(
459474
"includefile",
460475
"+pypi_include/include/includefile",
461-
["include.h"],
476+
[_file("../+pypi_include/include/includefile/include.h")],
462477
kind =
463478
VenvSymlinkKind.INCLUDE,
464479
),

0 commit comments

Comments
 (0)