Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b4e6496
basic impl
rickeylev Apr 23, 2026
c676d6e
fix tests
rickeylev Apr 23, 2026
10ace3b
dont pass select to repo-generated init creation
rickeylev Apr 25, 2026
0fd7c6d
fix testing on workspace
rickeylev Apr 25, 2026
7d2893f
Merge branch 'main' of https://github.com/bazel-contrib/rules_python …
rickeylev Apr 25, 2026
16c5100
format, lint fix
rickeylev Apr 25, 2026
9e53c53
fix pip example
rickeylev Apr 25, 2026
76cc0e0
format
rickeylev Apr 25, 2026
9470aac
test: fix windows paths for whl_with_data test
rickeylev Apr 25, 2026
5b57de2
fix: prevent grouping of top-level bin, include, and data in venvs
rickeylev Apr 25, 2026
103ec9a
fix: add alias for is_venvs_site_packages and add whl_with_data to wo…
rickeylev Apr 25, 2026
f41bfda
fix: add WORKSPACE file and export all files in whl_with_data repo
rickeylev Apr 25, 2026
fe1ad5f
revert: rename of is_venvs_site_packages and whl_with_data workspace …
rickeylev Apr 25, 2026
b35951a
fix: add bazel_skylib to pip_parse_vendored example
rickeylev Apr 25, 2026
52dfac5
fix: add bazel_skylib to multi_python_versions and pip_parse examples
rickeylev Apr 25, 2026
45611a5
docs: add DATA field to VenvSymlinkKind docstring
rickeylev Apr 25, 2026
fd6cda6
fix: undefined data_arg in whl_library_targets.bzl
rickeylev Apr 25, 2026
958e61e
fix: robustness in venvs_site_packages_libs_test for Windows paths
rickeylev Apr 25, 2026
194693c
fix: make bin/s3cmd expectation conditional in pip_parse_test.py
rickeylev Apr 25, 2026
82b9acc
refactor: rename is_venvs_site_packages to _is_venvs_site_packages
rickeylev Apr 25, 2026
dd2b0b3
pass create_inits() non-selet value, add select value afterwards
rickeylev Apr 25, 2026
3ed727e
change test to enforce windows uses Scripts/Include in venv
rickeylev Apr 25, 2026
7561c72
make pip_parse_test.py work with venv_site_packages
rickeylev Apr 25, 2026
24a94a2
add second wheel with data to test merging/overlap. rename first
rickeylev Apr 25, 2026
1fabeea
format
rickeylev Apr 25, 2026
473966b
revert passing bzlmod/venv_site_packages to pip_parse_test. not neede…
rickeylev Apr 25, 2026
dd5d8c2
remove defunct comment
rickeylev Apr 26, 2026
08aeea4
(re)add _yes suffix to is_venv_site_packages config setting
rickeylev Apr 26, 2026
747f7cb
add basis for data overlap test
rickeylev Apr 26, 2026
03da47f
add overlap tests for include, bin; cleanup test
rickeylev Apr 26, 2026
de2a11f
format
rickeylev Apr 26, 2026
82e9e2a
make tests pass with workspace
rickeylev Apr 26, 2026
cdb2cf1
lint
rickeylev Apr 26, 2026
e7ca80d
tests: support whl_from_dir_repo on Windows
rickeylev Apr 26, 2026
e16757e
Merge branch 'main' into chore.win.whl.tests
rickeylev Apr 26, 2026
e35d1a4
move data scheme to venv root
rickeylev Apr 26, 2026
e38fd1c
move cannot_be_linked_directly init earlier
rickeylev Apr 26, 2026
9184bcc
re-add data prefix to pip_parse test. It is verifying the whl_library…
rickeylev Apr 26, 2026
db05136
Merge branch 'main' of https://github.com/bazel-contrib/rules_python …
rickeylev Apr 26, 2026
51f614b
handle when directly linking to a file on windows
rickeylev Apr 26, 2026
9681318
always incldue data label
rickeylev Apr 26, 2026
50cc8f0
update changelog
rickeylev Apr 26, 2026
cd18acc
cleanup
rickeylev Apr 26, 2026
8c3daaa
Merge branch 'chore.win.whl.tests' into whl.with.data
rickeylev Apr 26, 2026
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
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ use_repo(
"somepkg_with_build_files",
"whl_library_extras_direct_dep",
"whl_with_build_files",
"whl_with_data",
)

dev_rules_python_config = use_extension(
Expand Down
2 changes: 1 addition & 1 deletion python/config_settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ string_flag(
)

config_setting(
name = "is_venvs_site_packages",
name = "_is_venvs_site_packages_yes",
flag_values = {
":venvs_site_packages": VenvsSitePackages.YES,
},
Expand Down
11 changes: 11 additions & 0 deletions python/private/internal_dev_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ def _internal_dev_deps_impl(mctx):
enable_implicit_namespace_pkgs = False,
)

whl_from_dir_repo(
name = "whl_with_data_whl",
root = "//tests/repos/whl_with_data:BUILD.bazel",
output = "whl_with_data-1.0-any-none-any.whl",
)
whl_library(
name = "whl_with_data",
whl_file = "@whl_with_data_whl//:whl_with_data-1.0-any-none-any.whl",
requirement = "whl-with-data",
)

_whl_library_from_dir(
name = "whl_library_extras_direct_dep",
root = "//tests/pypi/whl_library/testdata/pkg:BUILD.bazel",
Expand Down
23 changes: 15 additions & 8 deletions python/private/py_executable.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -576,8 +576,10 @@ def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root
)

venv_dir_map = {
VenvSymlinkKind.BIN: venv_details.bin_dir,
VenvSymlinkKind.BIN: "{}/{}".format(venv_ctx_rel_root, venv_details.bin_dir),
VenvSymlinkKind.LIB: site_packages,
VenvSymlinkKind.INCLUDE: "{}/{}".format(venv_ctx_rel_root, venv_details.include_dir),
VenvSymlinkKind.DATA: "{}/data".format(venv_ctx_rel_root),
}
venv_app_files = create_venv_app_files(
ctx,
Expand Down Expand Up @@ -659,7 +661,7 @@ def _create_venv_unixy(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_pa

recreate_venv_at_runtime = False

bin_dir = "{}/bin".format(venv_ctx_rel_root)
venv_bin_ctx_rel_path = "{}/bin".format(venv_ctx_rel_root)
if create_full_venv:
# Some wrappers around the interpreter (e.g. pyenv) use the program
# name to decide what to do, so preserve the name.
Expand All @@ -671,15 +673,15 @@ def _create_venv_unixy(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_pa
# When the venv symlinks are disabled, the $venv/bin/python3 file isn't
# needed or used at runtime. However, the zip code uses the interpreter
# File object to figure out some paths.
interpreter = ctx.actions.declare_file("{}/{}".format(bin_dir, py_exe_basename))
interpreter = ctx.actions.declare_file("{}/{}".format(venv_bin_ctx_rel_path, py_exe_basename))
ctx.actions.write(interpreter, "actual:{}".format(interpreter_actual_path))

elif runtime.interpreter:
# Even though ctx.actions.symlink() is used, using
# declare_symlink() is required to ensure that the resulting file
# in runfiles is always a symlink. An RBE implementation, for example,
# may choose to write what symlink() points to instead.
interpreter = ctx.actions.declare_symlink("{}/{}".format(bin_dir, py_exe_basename))
interpreter = ctx.actions.declare_symlink("{}/{}".format(venv_bin_ctx_rel_path, py_exe_basename))
interpreter_runfiles.add(interpreter)

rel_path = relative_path(
Expand All @@ -690,7 +692,7 @@ def _create_venv_unixy(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_pa
)
ctx.actions.symlink(output = interpreter, target_path = rel_path)
else:
interpreter = ctx.actions.declare_symlink("{}/{}".format(bin_dir, py_exe_basename))
interpreter = ctx.actions.declare_symlink("{}/{}".format(venv_bin_ctx_rel_path, py_exe_basename))
interpreter_runfiles.add(interpreter)
ctx.actions.symlink(output = interpreter, target_path = runtime.interpreter_path)
else:
Expand All @@ -715,7 +717,8 @@ def _create_venv_unixy(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_pa
interpreter = interpreter,
pyvenv_cfg = pyvenv_cfg,
site_packages = site_packages,
bin_dir = bin_dir,
bin_dir = "bin",
include_dir = "include",
recreate_venv_at_runtime = recreate_venv_at_runtime,
interpreter_runfiles = interpreter_runfiles.build(ctx),
interpreter_symlinks = depset(),
Expand Down Expand Up @@ -777,7 +780,8 @@ def _create_venv_windows(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_
interpreter = interpreter,
pyvenv_cfg = None,
site_packages = site_packages,
bin_dir = venv_bin_ctx_rel_path,
bin_dir = venv_bin_rel_path,
include_dir = "Include",
recreate_venv_at_runtime = True,
interpreter_runfiles = interpreter_runfiles.build(ctx),
interpreter_symlinks = interpreter_symlinks.build(),
Expand All @@ -789,6 +793,7 @@ def _venv_details(
pyvenv_cfg,
site_packages,
bin_dir,
include_dir,
recreate_venv_at_runtime,
interpreter_runfiles,
interpreter_symlinks):
Expand All @@ -801,8 +806,10 @@ def _venv_details(
pyvenv_cfg = pyvenv_cfg,
# str; venv-relative path to the site-packages directory
site_packages = site_packages,
# str; ctx-relative path to the venv's bin directory.
# str; venv-relative path to the venv's bin directory.
bin_dir = bin_dir,
# str; venv-relative-path to the venv's include directory.
include_dir = include_dir,
# bool; True if the venv needs to be recreated at runtime (because the
# build-time construction isn't sufficient). False if the build-time
# constructed venv is sufficient.
Expand Down
1 change: 1 addition & 0 deletions python/private/py_info.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ VenvSymlinkKind = struct(
BIN = "BIN",
LIB = "LIB",
INCLUDE = "INCLUDE",
DATA = "DATA",
)

def _VenvSymlinkEntry_init(**kwargs):
Expand Down
14 changes: 11 additions & 3 deletions python/private/pypi/whl_library_targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ _BAZEL_REPO_FILE_GLOBS = [
"WORKSPACE.bazel",
]

_IS_VENV_SITE_PACKAGES_YES = Label("//python/config_settings:_is_venvs_site_packages_yes")

def whl_library_targets_from_requires(
*,
name,
Expand Down Expand Up @@ -194,8 +196,10 @@ def whl_library_targets(
DIST_INFO_LABEL: dict(
include = ["site-packages/*.dist-info/**"],
),

## TO CHECK: should bin/ and include/ be part of the data target?
Comment thread
rickeylev marked this conversation as resolved.
Outdated
DATA_LABEL: dict(
include = ["data/**"],
include = ["data/**", "bin/**", "include/**"],
),
}

Expand Down Expand Up @@ -356,11 +360,15 @@ def whl_library_targets(
if item not in _data_exclude:
_data_exclude.append(item)

data = data + native.glob(
data = data + [":" + DATA_LABEL] + native.glob(
["site-packages/**/*"],
exclude = _data_exclude,
allow_empty = True,
)
data = data + select({
_IS_VENV_SITE_PACKAGES_YES: [DATA_LABEL],
"//conditions:default": [],
})
Comment thread
rickeylev marked this conversation as resolved.
Outdated

pyi_srcs = native.glob(
["site-packages/**/*.pyi"],
Expand All @@ -369,7 +377,7 @@ def whl_library_targets(

if not enable_implicit_namespace_pkgs:
generated_namespace_package_files = select({
Label("//python/config_settings:is_venvs_site_packages"): [],
_IS_VENV_SITE_PACKAGES_YES: [],
"//conditions:default": rules.create_inits(
srcs = srcs + data + pyi_srcs,
ignored_dirnames = [], # If you need to ignore certain folders, you can patch rules_python here to do so.
Expand Down
31 changes: 27 additions & 4 deletions python/private/venv_runfiles.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,16 @@ def _get_file_venv_path(ctx, f, site_packages_root):

Returns:
A tuple `(venv_path, rf_root_path)` if the file is under
`site_packages_root`, otherwise `(None, None)`.
`site_packages_root` or data/, bin/, include/ otherwise `(None, None)`.
"""
rf_root_path = runfiles_root_path(ctx, f.short_path)
_, _, repo_rel_path = rf_root_path.partition("/")

# Check for wheel data/bin/include folders first
for prefix in ["data/", "bin/", "include/"]:
if repo_rel_path.startswith(prefix):
return (repo_rel_path, rf_root_path)

head, found_sp_root, venv_path = repo_rel_path.partition(site_packages_root)
if head or not found_sp_root:
# If head is set, then the path didn't start with site_packages_root
Expand Down Expand Up @@ -452,19 +458,36 @@ def get_venv_symlinks(

# Finally, for each group, we create the VenvSymlinkEntry objects
for venv_path, files in optimized_groups.items():
if venv_path.startswith("data/"):
out_venv_path = venv_path[len("data/"):]
kind = VenvSymlinkKind.DATA
prefix = ""
elif venv_path.startswith("include/"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about windows wheels? Do we not need to check for capitalized includes?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because the wheel extraction always uses unix names. Platform-specific renaming happens at the py_binary level.

Admittedly, I'm not entirely happy with this block of logic because it assumes how whl_library extracted the contents. Just another good reason we need to create a py_extracted_wheel_library rule

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule would be taking what as an input? The wheel or extracted contents of the wheel? You mean the moving of the files around could be done in the build phase? I would definitely +1 that. With pipstar being the only code path that might be doable, but might require a 3.0 release.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whl_library would read entry point.txt and generate a target for each entry point. This target generates a platform aware/specific file (which shows up as a KIND: BIN entry in PyInfo), but . This file is venv agnostic (by way of the polyglot trick or equivalent). Py binary can then just copy/symlink that file into the venv bin directory.

That's my rough thinking anyways

out_venv_path = venv_path[len("include/"):]
kind = VenvSymlinkKind.INCLUDE
prefix = ""
elif venv_path.startswith("bin/"):
out_venv_path = venv_path[len("bin/"):]
kind = VenvSymlinkKind.BIN
prefix = ""
Comment thread
rickeylev marked this conversation as resolved.
else:
out_venv_path = venv_path
kind = VenvSymlinkKind.LIB
prefix = site_packages_root

link_to_path = (
_get_label_runfiles_repo(ctx, files[0].owner) +
"/" +
site_packages_root +
prefix +
venv_path
)
venv_symlinks[venv_path] = VenvSymlinkEntry(
kind = VenvSymlinkKind.LIB,
kind = kind,
link_to_path = link_to_path,
link_to_file = None,
package = package,
version = version_str,
venv_path = venv_path,
venv_path = out_venv_path,
files = depset(files),
)

Expand Down
12 changes: 6 additions & 6 deletions tests/pypi/whl_library_targets/whl_library_targets_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def _test_whl_and_library_deps_from_requires(env):
env.expect.that_dict(py_library_call).contains_exactly({
"name": "pkg",
"srcs": ["site-packages/foo/SRCS.py"] + select({
Label("//python/config_settings:is_venvs_site_packages"): [],
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
"//conditions:default": ["_create_inits_target"],
}),
"pyi_srcs": ["site-packages/foo/PYI.pyi"],
Expand All @@ -259,7 +259,7 @@ def _test_whl_and_library_deps_from_requires(env):
"visibility": ["//visibility:public"],
"experimental_venvs_site_packages": Label("//python/config_settings:venvs_site_packages"),
"namespace_package_files": [] + select({
Label("//python/config_settings:is_venvs_site_packages"): [],
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
"//conditions:default": ["_create_inits_target"],
}),
}) # buildifier: @unsorted-dict-items
Expand Down Expand Up @@ -361,7 +361,7 @@ def _test_whl_and_library_deps(env):
env.expect.that_dict(py_library_calls[0]).contains_exactly({
"name": "pkg",
"srcs": ["site-packages/foo/SRCS.py"] + select({
Label("//python/config_settings:is_venvs_site_packages"): [],
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
"//conditions:default": ["_create_inits_target"],
}),
"pyi_srcs": ["site-packages/foo/PYI.pyi"],
Expand All @@ -386,7 +386,7 @@ def _test_whl_and_library_deps(env):
"visibility": ["//visibility:public"],
"experimental_venvs_site_packages": Label("//python/config_settings:venvs_site_packages"),
"namespace_package_files": [] + select({
Label("//python/config_settings:is_venvs_site_packages"): [],
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
"//conditions:default": ["_create_inits_target"],
}),
}) # buildifier: @unsorted-dict-items
Expand Down Expand Up @@ -444,7 +444,7 @@ def _test_group(env):
).contains_exactly({
"name": "_pkg",
"srcs": ["site-packages/foo/srcs.py"] + select({
Label("//python/config_settings:is_venvs_site_packages"): [],
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
"//conditions:default": ["_create_inits_target"],
}),
"pyi_srcs": ["site-packages/foo/pyi.pyi"],
Expand All @@ -459,7 +459,7 @@ def _test_group(env):
"visibility": ["@pypi__config//_groups:__pkg__"],
"experimental_venvs_site_packages": Label("//python/config_settings:venvs_site_packages"),
"namespace_package_files": [] + select({
Label("//python/config_settings:is_venvs_site_packages"): [],
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
"//conditions:default": ["_create_inits_target"],
}),
}) # buildifier: @unsorted-dict-items
Expand Down
1 change: 1 addition & 0 deletions tests/repos/whl_with_data/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(glob(["*"]))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .data/data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .data/headers
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .data/platlib
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#!/bin/sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Metadata-Version: 2.1
Name: whl-with-data
Version: 1.0
6 changes: 6 additions & 0 deletions tests/repos/whl_with_data/whl_with_data-1.0.dist-info/RECORD
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
whl_with_data-1.0.data/platlib/whl_with_data/platlib_file.txt,sha256=123,123
whl_with_data-1.0.data/scripts/whl_script.sh,sha256=123,123
whl_with_data-1.0.data/headers/whl_with_data/header_file.h,sha256=123,123
whl_with_data-1.0.data/purelib/whl_with_data/data_file.txt,sha256=123,123
whl_with_data-1.0.data/data/whl_with_data/data_data_file.txt,sha256=123,123
whl_with_data-1.0.data/data/whl_with_data/data_data_file.txt,sha256=123,123
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Wheel-Version: 1.0
1 change: 1 addition & 0 deletions tests/venv_site_packages_libs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ py_reconfig_test(
"@other//nspkg_gamma",
"@other//nspkg_single",
"@other//with_external_data",
"@whl_with_data//:pkg",
],
)

Expand Down
29 changes: 28 additions & 1 deletion tests/venv_site_packages_libs/bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_dirs_from_replaced_package_are_not_present(self):
module_path = Path(module.__file__)

site_packages = module_path.parent.parent
dist_info_dirs = [p.name for p in site_packages.glob("*.dist-info")]
dist_info_dirs = [p.name for p in site_packages.glob("simple*.dist-info")]
self.assertEqual(
["simple-1.0.0.dist-info"],
dist_info_dirs,
Expand All @@ -91,6 +91,33 @@ def test_data_from_another_pkg_is_included_via_copy_file(self):
files = [p.name for p in d.glob("*")]
self.assertIn("another_module_data.txt", files)

def test_whl_with_data_included(self):
module = self.assert_imported_from_venv("whl_with_data")
module_path = Path(module.__file__)
site_packages = module_path.parent.parent

# purelib
data_file = site_packages / "whl_with_data" / "data_file.txt"
self.assertTrue(data_file.exists(), f"Expected {data_file} to exist")

# platlib
platlib_file = site_packages / "whl_with_data" / "platlib_file.txt"
self.assertTrue(platlib_file.exists(), f"Expected {platlib_file} to exist")

venv_root = Path(self.venv)

# data
data_data_file = venv_root / "data" / "whl_with_data" / "data_data_file.txt"
self.assertTrue(data_data_file.exists(), f"Expected {data_data_file} to exist")

# scripts
script_file = venv_root / "bin" / "whl_script.sh"
self.assertTrue(script_file.exists(), f"Expected {script_file} to exist")

# headers
header_file = venv_root / "include" / "whl_with_data" / "header_file.h"
self.assertTrue(header_file.exists(), f"Expected {header_file} to exist")


if __name__ == "__main__":
unittest.main()
Loading