Repository navigation
Canonical way to isolate binary info from PyExecutableInfo聽#3324
Description
馃殌 feature request
Relevant Rules
Adding information to PyExecutableInfo (impacts py_binary, py_test).
Description
Adding something to PyExecutableInfo that helps communicate "here's extra stuff for the binary that wouldn't be present were this a library".
Right now I have a custom rules that takes deps which expose PyInfo (which py_binary does).
It then executes some custom logic stemming from something like this:
inputs = depset(
transitive = [dep[DefaultInfo].data_runfiles.files for dep in ctx.attr.deps] +
[dep[DefaultInfo].default_runfiles.files for dep in ctx.attr.deps],
)After some more processing of that depset named "inputs", it then exposes them to other rules which seek to cp or symlink those files around.
This fails due to errors such as:
FileNotFoundError: [Errno 2] No such file or directory: 'bazel-out/darwin_arm64-fastbuild/bin/tools/runnable/examples/python/_example_app_bin.venv/bin/python3'
This only occurs with build --@rules_python//python/config_settings:bootstrap_impl=script.
Describe the solution you'd like
It sounds like I generally need to re-work my rules because accepting the heterogeneous inputs of Pyinfo vs PyInfo AND PyExecutableInfo will need some better handling on my end, but ideally there is a canonical way for me to remove any extra things that are only necessary because something is a binary dependency.
Please see this Slack thread for extra context: https://bazelbuild.slack.com/archives/CA306CEV6/p1759447665927069?thread_ts=1759177072.316529&cid=CA306CEV6
If PyExecutableInfo was modified to add something like
PyExecutableInfo = provider(
fields = {
# ... existing fields ...
"content_files": """
:type: depset[File]
The actual content files (sources, data) without runtime scaffolding.
This excludes venv directories, bootstrap scripts, and executable wrappers.
""",
}
)then I think my use case would be unblocked relatively nicely.
Describe alternatives you've considered
Right now I have added some workarounds to my custom rule to check for the presence of PyExecutableInfo and then do some heuristic filtering of paths based on patterns that I've seen.
This seems to work but feels non ideal for obvious reasons
def _is_runtime_artifact(file):
"""
Identify files that are runtime scaffolding, not actual content.
This cannot identify the actual shell script that `py_binary`
generates, but that is largely static and small, and more importantly it exists
at build time, so there is no real harm in including it.
The bootstrap script is also harmless to keep in but we remove it for completeness.
"""
path = file.short_path
basename = file.basename
# Venv artifacts added by `rules_python` with bootstrap_impl=script
if path.endswith(".venv/bin/python3"):
return True
# Bootstrap scripts added by `rules_python` with bootstrap_impl=script
if basename.endswith("_stage2_bootstrap.py"):
return True
return False
Activity
rickeylev commentedon Oct 8, 2025
At a high level, my thinking is:
Where it gets tricky is DefaultInfo. That has to have a default output of the executable, and its runfiles have to contain the required executable files (executable, stage2 bootstrap, venv files, python runtime files, and transitive runfiles). The only way I can see to deal with this is for PyExecutableInfo to have a field with an alternative DefaultInfo. Such that one can then do:
I'm not entirely sure how the "binary specific files" needs to be broken out on PEI though. There's the following "categories" of files I can think of:
bin/<other entries>files_bazel_site_init.pthandbazel_site_init.pyThat's a lot of fields, though. I'm not sure which are too broad or too granular. Some are specific to the particular toolchain implementation (e.g. stage2 bootstrap).
Something that might help are some concrete use cases for the types of transforms/derivations being done. Some things that have crossed my mind:
FrankPortman commentedon Oct 8, 2025
A few raw thoughts if it helps.
This is the shell script? Yea this is tough to remove automatically on my end since it is named depending on the binary name - although perhaps I can do some extra magic inside my internal rule to derive that given detection of PEI. I probably wouldn't bother in my internal rules through because that shell script is harmless enough for now.
easy to detet manually
Yep it does seem special - do you have any idea why? This is the one that is always broken as "missing" if my rule tried to naively
cpdata around.Funnily enough, for my use case, I separate between internal deps and external + pypi deps (with the help of a
rules_pythonpatch), and so all the other stuff just falls out pretty naturally as being "unowned" by my repo. So, for now its not bothering me, and depending on how it is fixed it may actually break my custom rule (but I assume I can adapt).These are treated as owned by my repo and thus included in my artifacts, but seem harmless enough to include. But I think they can be detected statically if I feel strongly - need to double check.
Don't think I've seen this one.
I can give you a high level description of my use cases. We have been treating
py_binaryas a canonical concept of a "runnable" that we then hand to an internal rule which dresses it up, exposes the underlying localpy_binaryforbazel runpurposes, and then stubs out some providers for downstream rules to consume for use cases such as:requirements.txtfile and themainof thepy_binaryas the entrypointrules_pythonpatch described above in this commentThe biggest alternative I have considered so far, and I am curious if you think this is just worth moving to in terms of simplifying everything, is to just create a new macro which has all the same attrs as
py_binary, but makes sure to stub out apy_libraryinternally, so that a lot of the headaches described in this issue go away. Maybe this is just the path forward - I like the idea of depending on apy_binaryas it respects the foundational unit fromrules_python, but if wrapping it internally clears away a lot of headaches, I am very open to it.FrankPortman commentedon Oct 8, 2025
Something like this would be fine for me I think. I think it's a reasonable cost for custom rules if they want to support both types of inputs.
rickeylev commentedon Oct 8, 2025
For the executable, you can use DefaultInfo.files_to_run.executable to identify it
Under the hood, with bootstrap=script, relative symlinks are used to make bin/python3 refer back to the underlying python interpreter elsewhere in runfiles (e.g.
$runfiles/foo.venv/bin/python3 -> ../../+python_linux_x86/bin/python). Similar is done for other entries in the venv (when--venvs_site_package_libs=yes). If you're on Bazel 8, you can identify such files with File.is_symlink().Ah, I think because the first stage bootstrap relies on it. It goes looking for the interpreter to use, so it has e.g.
_main/foo.venv/bin/python3embedded into it as the place to go looking. If one were going to derive a new executable, then you'd need to know the path tobin/python3(for most cases, anyways).FrankPortman commentedon Oct 8, 2025
Interesting - I wonder if my workaround can be pretty principled then by using
DefaultInfo.files_to_run.executablefor the executable, checking for symlinks for the.venv/bin/python3, and then relying on my existing separation of internal vs internal deps for the site package stuff. It's a mouthful but might get me what i need. I am mainly focused on the "Create a wheel with internal vs external deps + entrypoint" case for now though so maybe I am missing something about the other cases I described (which are more forward looking for us).FrankPortman commentedon Dec 2, 2025
@rickeylev I'd be open to taking a stab at a PR for this if you can share some guidance on what the providers should look like.
rickeylev commentedon Jan 31, 2026
FYI: #3539 adds a variety of fields to PyExecutableInfo to aid packaging. Notably, it provides separate fields for the python runtime, deps added by the user, and the entry point and bootstraps. Looking at the py_zipapp_rule.bzl code might be informative for how to piece them together.
FrankPortman commentedon Jan 31, 2026
Nice, that could completely close this ticket iiuc. Happy to test once that lands.
feat: add py_zipapp_binary and test rules for zipapp support (#3539)
rickeylev commentedon Feb 7, 2026
@FrankPortman The PyExecutableInfo fields are landed at HEAD.
Since enough has been exposed to re-package into a zipapp, I'm going to mark this as complete.