Skip to content

feat(venv): Static site-packages trees #551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

arrdem
Copy link
Contributor

@arrdem arrdem commented Apr 22, 2025

This PR introduces a new suite of virtualenv machinery building on the existing py_binary virtualenv tree builder to create such trees "statically" at build time rather than "dynamically" at runtime. The original reason this is difficult is that the $VENV/bin/python file is usually a link to a static interpreter, and the interpreter specified in pyvenv.cfg is also usually an absolute path to a real interpreter outside of the venv.

The introduction of #546 or something like it makes it possible to create a virtualenv tree which does not refer to an interpreter with a fixed path. Instead an appropriate interpreter can be dynamically discovered by $PATH inspection. Support for other configuration ala /etc/pexrc to facilitate interpreter discovery is possible but beyond the scope of an initial implementation.

By using an interpreter shim, the pyenv.cfg file can use a relative path to ./bin/python as its interpreter reference, and the ./bin/python3 and ./bin/python3.X links can be created statically pointing to the shim. As long as other bin/ entrypoint scripts reference only #!/usr/bin/env python3 as is fairly conventional, the constructed virtual environment will contain no absolute references to a specific interpreter and will be fully relocatable.

A relocatable virtualenv tree containing dependencies installed into the site-packages tree may be built using the new experimental py_venv rule.

py_venv(
    name = "venv",
    deps = [
        ... # PyInfo targets to be integrated into the site-packages tree
    ],
)

Files are selected for inclusion into virtualenv based on import paths.

  • Any import path which contains a *.dist-info child directory will have its contents included.
  • Any included path which ends in site-packages and for which there exist files in a sibling bin or other conventional directory will have those assets included into the virtualenv.

These heuristics are chosen to both handle how rules_python currently emulates package installation, and to avoid dependencies on external workspace specific behavior so that internal or vendored libraries can be "installed" into the venv.

A py_venv is not intended to be a .runfiles-style packaging of all source code, it is intended to be a packaging of dependency code which is compliant with being treated as if pip installed. This may not be generally true of firstparty application code, depending on usage of runfiles.Runfiles or repository-relative logics. Because of this py_venv targets provide files not relocated into the venv as runfiles dependencies.

Applications wishing to leverage a static virtualenv should leverage the py_venv_binary rule.

py_venv_binary(
    name = "demo",
    srcs = ["demo.py"],
    deps = [ ... ],
    main = "demo.py",
)

A py_venv_binary defines a more conventional .runfiles-based Python application, but one which will use the .runfiles embedded virtualenv as the source of thirdparty packages. Firstparty packages will receive the normal .runfiles workspace structure preserving treatment.

A py_venv_test is also provided which behaves the same as a py_venv_binary.

TODO

  • Get the implementation fully aligned with this writeup
  • Convert the existing example into a test
  • Create an example of using py_venv_binary with rules_oci together to create a static application

For follow-up work

Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: yes
  • Breaking change (forces users to change their own code or config): no
  • Suggested release notes appear below: yes

Test plan

  • New test cases added

Copy link

github-actions bot commented Apr 22, 2025

e2e/use_release folder: LCOV of commit 22c91c7 during CI #1739

Summary coverage rate:
  lines......: 100.0% (2 of 2 lines)
  functions..: 100.0% (1 of 1 function)
  branches...: no data found

Files changed coverage rate: n/a

Copy link

aspect-workflows bot commented Apr 22, 2025

Test

All tests were cache hits

32 tests (100.0%) were fully cached saving 59s.

@arrdem arrdem force-pushed the arrdem/relocatable-venv-shim branch from e4f8f87 to cdbe199 Compare April 28, 2025 18:50
Base automatically changed from arrdem/relocatable-venv-shim to main April 28, 2025 19:05
@arrdem arrdem force-pushed the arrdem/feat-static-site-packages branch 2 times, most recently from 5672208 to 128642f Compare April 28, 2025 19:13
@arrdem arrdem force-pushed the arrdem/feat-static-site-packages branch from 128642f to 9cfdd0b Compare April 29, 2025 00:36
@arrdem arrdem requested review from alexeagle and thesayyn April 29, 2025 03:51
@arrdem arrdem marked this pull request as ready for review April 29, 2025 04:38
@arrdem arrdem force-pushed the arrdem/feat-static-site-packages branch from ed05c90 to a5313f7 Compare May 1, 2025 15:55
),
})

def _python_version_transition_impl(_, attr):
Copy link
Member

Choose a reason for hiding this comment

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

can this transition live in a single place?


echo "Created virtualenv in ${VIRTUAL_ENV}"
exec "$(rlocation "{{VENV}}")"/bin/python {{INTERPRETER_FLAGS}} "$@"
Copy link
Member

Choose a reason for hiding this comment

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

previously this rule was used for IDE support via bazel run :venv to create the .venv symlink in the source tree. we are probably breaking those people here.

Copy link
Member

@thesayyn thesayyn left a comment

Choose a reason for hiding this comment

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

Overall looking good, a few things, rust can be better but i think its good enough for now.

imports_depset = _py_library.make_imports_depset(ctx, extra_imports_depsets = virtual_resolution.imports)

pth_lines = ctx.actions.args()
pth_lines.use_param_file("%s", use_always = True)
pth_lines.use_param_file("%s/", use_always = True)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pth_lines.use_param_file("%s/", use_always = True)
pth_lines.use_param_file("%s", use_always = True)

# which ends up in bazel_tools/tools/python/runfiles/runfiles.py, but there are no imports attrs that hint we
# should be adding the root to the PYTHONPATH
# Maybe in the future we can opt out of this?
pth_lines.add(".")
Copy link
Member

Choose a reason for hiding this comment

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

lets double check if this is breaking

VenvMode::StaticPth => {
let venv = py::venv::create_empty_venv(
&args.python,
py::venv::PythonVersionInfo::from_str(&args.version.unwrap())?,
Copy link
Member

Choose a reason for hiding this comment

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

.unwrap() here sounds dangerous. do we want to check and print an error message?

@thesayyn
Copy link
Member

thesayyn commented May 8, 2025

LGTM, nice job!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants