Skip to content

Commit

Permalink
feat: add a virtual environment rule
Browse files Browse the repository at this point in the history
  • Loading branch information
oxidase committed Feb 22, 2024
1 parent 194e9be commit 407fb97
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 1 deletion.
8 changes: 7 additions & 1 deletion python/BUILD
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
py_binary(
name = "poetry_deps",
srcs = ["poetry_deps.py"],
visibility = ["//visibility:public"],
visibility = ["__subpackages__"],
deps = [
"@rules_poetry_pip//:pkg",
],
)

py_binary(
name = "py_venv",
srcs = ["py_venv.py"],
visibility = ["__subpackages__"],
)
48 changes: 48 additions & 0 deletions python/py_venv.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
def _py_venv_impl(ctx):
"""
Rule to link Python package into a virtual environment.
Arguments:
ctx: The rule context.
Attributes:
deps: label_list The package dependencies list.
Private attributes:
_py_venv:
Returns:
The providers list or a tuple with a venv package.
"""

deps = ctx.attr.deps
output = ctx.actions.declare_directory("venv/{}".format(ctx.label.name))
import_paths = ["{}/external/{}".format(ctx.bin_dir.path, path) for dep in deps for path in dep[PyInfo].imports.to_list()]

ctx.actions.run(
outputs = [output],
inputs = ctx.files.deps,
mnemonic = "CreateVenv",
progress_message = "Creating venv {}".format(ctx.label.name),
arguments = [output.path] + import_paths,
use_default_shell_env = True,
executable = ctx.executable._py_venv,
)

transitive_depsets = [dep[PyInfo].transitive_sources for dep in deps]
runfiles = [output] + [item for dep in transitive_depsets for item in dep.to_list()]
files = depset([output], transitive = transitive_depsets)

return [
DefaultInfo(files = files, runfiles = ctx.runfiles(files = runfiles)),
PyInfo(transitive_sources = files, imports = depset(["_main/" + output.short_path])),
]

py_venv = rule(
implementation = _py_venv_impl,
provides = [PyInfo],
attrs = {
"deps": attr.label_list(doc = "The package dependencies list"),
"_py_venv": attr.label(default = ":py_venv", cfg = "exec", executable = True),
},
)
28 changes: 28 additions & 0 deletions python/py_venv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import argparse
from pathlib import Path

SKIP_SET = {Path("requirements.txt")}

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Download and install a Poetry package")

parser.add_argument("target", type=Path, help="output virtual environment directory")
parser.add_argument("path", type=Path, nargs="*", help="python package path")

args = parser.parse_args()

for python_path in args.path:
if not python_path.exists() or not python_path.is_dir():
raise RuntimeError(f"Required Python package directory {path} does not exist")

for directory_path, _, file_names in python_path.walk():
in_package_directory = directory_path.relative_to(python_path)
target_directory = args.target / in_package_directory
target_directory.mkdir(parents=True, exist_ok=True)
relative_directory = directory_path.relative_to(target_directory, walk_up=True)

for file_name in file_names:
if in_package_directory / file_name not in SKIP_SET:
symlink_path = target_directory / file_name
target_path = relative_directory / file_name
symlink_path.symlink_to(target_path)

0 comments on commit 407fb97

Please sign in to comment.