fix(--debugger): Ensure that imports or venv_site_package files are propagated for debugger target (#3483)
https://github.com/bazel-contrib/rules_python/commit/a94bd0fdde426bf30efed7c819422d74b404cc18
recently added support for injecting dependencies for easier use of
debugger. It allows injecting deps via
`--@rules_python//python/config_settings:debugger=<target>`. While the
runfiles from `<target>` were inherited in the final binary, the
`imports` or `venv_site_packages` were missing. Hence making the
debugger target unusable for various corner cases (e.g. when it uses
`imports = ...` or when it is coming from pip hub and
`venv_site_packages` are on).
This PR fixes that, and extends the unit test to include this situation.
Fixes: https://github.com/bazel-contrib/rules_python/issues/3481
---------
Co-authored-by: Shayan Hoshyari <hoshyari@adobe.com>
diff --git a/python/private/common.bzl b/python/private/common.bzl
index a593e97..c31aeb3 100644
--- a/python/private/common.bzl
+++ b/python/private/common.bzl
@@ -161,12 +161,8 @@
Returns:
CcInfo provider of merged information.
"""
- deps = ctx.attr.deps
- if extra_deps:
- deps = list(deps)
- deps.extend(extra_deps)
cc_infos = []
- for dep in deps:
+ for dep in collect_deps(ctx, extra_deps):
if CcInfo in dep:
cc_infos.append(dep[CcInfo])
@@ -175,17 +171,19 @@
return cc_common.merge_cc_infos(cc_infos = cc_infos)
-def collect_imports(ctx):
+def collect_imports(ctx, extra_deps = []):
"""Collect the direct and transitive `imports` strings.
Args:
ctx: {type}`ctx` the current target ctx
+ extra_deps: list of Target to also collect imports from.
Returns:
{type}`depset[str]` of import paths
"""
+
transitive = []
- for dep in ctx.attr.deps:
+ for dep in collect_deps(ctx, extra_deps):
if PyInfo in dep:
transitive.append(dep[PyInfo].imports)
if BuiltinPyInfo != None and BuiltinPyInfo in dep:
@@ -479,3 +477,19 @@
return short_path[3:]
else:
return "{}/{}".format(ctx.workspace_name, short_path)
+
+def collect_deps(ctx, extra_deps = []):
+ """Collect the dependencies from the rule's context.
+
+ Args:
+ ctx: rule ctx
+ extra_deps: list of Target to also collect dependencies from.
+
+ Returns:
+ list of Target
+ """
+ deps = ctx.attr.deps
+ if extra_deps:
+ deps = list(deps)
+ deps.extend(extra_deps)
+ return deps
diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl
index ea00eed..f9c9122 100644
--- a/python/private/py_executable.bzl
+++ b/python/private/py_executable.bzl
@@ -37,6 +37,7 @@
load(
":common.bzl",
"collect_cc_info",
+ "collect_deps",
"collect_imports",
"collect_runfiles",
"create_binary_semantics_struct",
@@ -293,7 +294,8 @@
runtime_details,
cc_details,
native_deps_details,
- runfiles_details):
+ runfiles_details,
+ extra_deps):
_ = is_test, cc_details, native_deps_details # @unused
is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints)
@@ -323,6 +325,7 @@
add_runfiles_root_to_sys_path = (
"1" if BootstrapImplFlag.get_value(ctx) == BootstrapImplFlag.SYSTEM_PYTHON else "0"
),
+ extra_deps = extra_deps,
)
stage2_bootstrap = _create_stage2_bootstrap(
@@ -486,7 +489,7 @@
# * https://snarky.ca/how-virtual-environments-work/
# * https://github.com/python/cpython/blob/main/Modules/getpath.py
# * https://github.com/python/cpython/blob/main/Lib/site.py
-def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root_to_sys_path):
+def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root_to_sys_path, extra_deps):
create_full_venv = BootstrapImplFlag.get_value(ctx) == BootstrapImplFlag.SCRIPT
venv = "_{}.venv".format(output_prefix.lstrip("_"))
@@ -587,7 +590,11 @@
VenvSymlinkKind.BIN: bin_dir,
VenvSymlinkKind.LIB: site_packages,
}
- venv_app_files = create_venv_app_files(ctx, ctx.attr.deps, venv_dir_map)
+ venv_app_files = create_venv_app_files(
+ ctx,
+ deps = collect_deps(ctx, extra_deps),
+ venv_dir_map = venv_dir_map,
+ )
files_without_interpreter = [pth, site_init] + venv_app_files.venv_files
if pyvenv_cfg:
@@ -1016,9 +1023,6 @@
default_outputs.add(precompile_result.keep_srcs)
default_outputs.add(required_pyc_files)
- imports = collect_imports(ctx)
-
- runtime_details = _get_runtime_details(ctx)
extra_deps = []
# The debugger dependency should be prevented by select() config elsewhere,
@@ -1026,6 +1030,8 @@
if not _is_tool_config(ctx):
extra_deps.append(ctx.attr._debugger_flag)
+ imports = collect_imports(ctx, extra_deps = extra_deps)
+ runtime_details = _get_runtime_details(ctx)
cc_details = _get_cc_details_for_binary(ctx, extra_deps = extra_deps)
native_deps_details = _get_native_deps_details(
ctx,
@@ -1058,6 +1064,7 @@
cc_details = cc_details,
native_deps_details = native_deps_details,
runfiles_details = runfiles_details,
+ extra_deps = extra_deps,
)
default_outputs.add(exec_result.extra_files_to_build)
diff --git a/tests/base_rules/py_executable_base_tests.bzl b/tests/base_rules/py_executable_base_tests.bzl
index 58251c6..2af5406 100644
--- a/tests/base_rules/py_executable_base_tests.bzl
+++ b/tests/base_rules/py_executable_base_tests.bzl
@@ -19,6 +19,7 @@
load("@rules_testing//lib:truth.bzl", "matching")
load("@rules_testing//lib:util.bzl", rt_util = "util")
load("//python:py_executable_info.bzl", "PyExecutableInfo")
+load("//python:py_info.bzl", "PyInfo")
load("//python:py_library.bzl", "py_library")
load("//python/private:common_labels.bzl", "labels") # buildifier: disable=bzl-visibility
load("//python/private:reexports.bzl", "BuiltinPyRuntimeInfo") # buildifier: disable=bzl-visibility
@@ -172,9 +173,11 @@
])
def _test_debugger(name, config):
+ # Using imports
rt_util.helper_target(
py_library,
name = name + "_debugger",
+ imports = ["."],
srcs = [rt_util.empty_file(name + "_debugger.py")],
)
@@ -187,24 +190,70 @@
labels.DEBUGGER: "//{}:{}_debugger".format(native.package_name(), name),
},
)
+
+ # Using venv
+ rt_util.helper_target(
+ py_library,
+ name = name + "_debugger_venv",
+ imports = [native.package_name() + "/site-packages"],
+ experimental_venvs_site_packages = "@rules_python//python/config_settings:venvs_site_packages",
+ srcs = [rt_util.empty_file("site-packages/" + name + "_debugger_venv.py")],
+ )
+
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject_venv",
+ srcs = [rt_util.empty_file(name + "_subject_venv.py")],
+ config_settings = {
+ # config_settings requires a fully qualified label
+ labels.DEBUGGER: "//{}:{}_debugger_venv".format(native.package_name(), name),
+ },
+ )
+
analysis_test(
name = name,
impl = _test_debugger_impl,
targets = {
"exec_target": name + "_subject",
"target": name + "_subject",
+ "target_venv": name + "_subject_venv",
},
attrs = {
"exec_target": attr.label(cfg = "exec"),
},
+ config_settings = {
+ labels.VENVS_SITE_PACKAGES: "yes",
+ labels.PYTHON_VERSION: "3.13",
+ },
)
_tests.append(_test_debugger)
def _test_debugger_impl(env, targets):
+ # 1. Subject
+
+ # Check the file from debugger dep is injected.
env.expect.that_target(targets.target).runfiles().contains_at_least([
"{workspace}/{package}/{test_name}_debugger.py",
])
+
+ # #3481: Ensure imports are setup correcty.
+ meta = env.expect.meta.derive(format_str_kwargs = {"package": targets.target.label.package})
+ env.expect.that_target(targets.target).has_provider(PyInfo)
+ imports = targets.target[PyInfo].imports.to_list()
+ env.expect.that_collection(imports).contains(meta.format_str("{workspace}/{package}"))
+
+ # 2. Subject venv
+
+ # #3481: Ensure that venv site-packages is setup correctly, if the dependency is coming
+ # from pip integration.
+ env.expect.that_target(targets.target_venv).runfiles().contains_at_least([
+ "{workspace}/{package}/_{name}.venv/lib/python3.13/site-packages/{test_name}_debugger_venv.py",
+ ])
+
+ # 3. Subject exec
+
+ # Ensure that tools don't inherit debugger.
env.expect.that_target(targets.exec_target).runfiles().not_contains(
"{workspace}/{package}/{test_name}_debugger.py",
)