Skip to content

Commit 0b03b0d

Browse files
Install Python packages data files to venv (cedarai#12)
* Install Python packages data files to venv
1 parent 115dcaf commit 0b03b0d

File tree

6 files changed

+55
-28
lines changed

6 files changed

+55
-28
lines changed

.bazelignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
example

build_env.py

+32-15
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import collections
15+
from enum import Enum
1616
import itertools
1717
import json
1818
import os
@@ -21,12 +21,20 @@
2121
import sys
2222
import textwrap
2323
import venv
24-
from typing import Dict, List, Optional
24+
from typing import Dict, List, NamedTuple
2525

2626
import importlib_metadata
2727

2828

29-
EnvFile = collections.namedtuple("EnvFile", ["path", "site_packages_path"])
29+
class EnvPathType(Enum):
30+
SITE_PACKAGES = 1
31+
DATA = 2
32+
33+
34+
class EnvFile(NamedTuple):
35+
path: pathlib.Path
36+
env_path: pathlib.Path
37+
type_: EnvPathType = EnvPathType.SITE_PACKAGES
3038

3139

3240
def console_script(env_path: pathlib.Path, module: str, func: str) -> str:
@@ -48,9 +56,9 @@ def path_starts_with(path: pathlib.Path, prefix: pathlib.Path) -> bool:
4856
return path.parts[: len(prefix.parts)] == prefix.parts
4957

5058

51-
def get_site_packages_path(
59+
def get_env_path(
5260
workspace: str, path: pathlib.Path, imports: List[pathlib.Path]
53-
) -> pathlib.Path:
61+
) -> EnvFile:
5462
# Import prefixes start with the workspace name, which might be the local workspace.
5563
# We first normalize the given path so that it starts with its workspace name.
5664
if path.parts[0] == "..":
@@ -62,20 +70,24 @@ def get_site_packages_path(
6270

6371
for imp in imports:
6472
if path_starts_with(wspath, imp):
65-
return wspath.relative_to(imp)
73+
return EnvFile(path, wspath.relative_to(imp))
74+
75+
imp_data = imp.parent / "data"
76+
if path_starts_with(wspath, imp_data):
77+
return EnvFile(path, wspath.relative_to(imp_data), EnvPathType.DATA)
6678

6779
if not is_external:
6880
# If the input wasn't an external path and it didn't match any import prefixes,
6981
# just return it as given.
70-
return path
82+
return EnvFile(path, path)
7183

7284
# External file that didn't match imports. Include but warn.
7385
# We include it as relative to its workspace directory, so strip the first component
7486
# off wspath.
7587
include_path = wspath.relative_to(wspath.parts[0])
7688
print(f"Warning: [{path}] didn't match any imports. Including as [{include_path}]")
7789

78-
return include_path
90+
return EnvFile(path, include_path)
7991

8092

8193
def is_external(file_: pathlib.Path) -> bool:
@@ -123,27 +135,32 @@ def get_files(build_env_input: Dict) -> List[EnvFile]:
123135
paths = [input_path]
124136

125137
for path in paths:
126-
site_packages_path = get_site_packages_path(workspace, path, imports)
127-
if site_packages_path != path or always_link:
128-
files.append(EnvFile(path, site_packages_path))
138+
env_file = get_env_path(workspace, path, imports)
139+
if env_file.env_path != path or always_link:
140+
files.append(env_file)
129141

130142
return files
131143

132144

133145
def is_data_file(file: EnvFile) -> bool:
134-
return file.site_packages_path.parts[0].endswith(".data")
146+
return (
147+
file.type_ == EnvPathType.DATA
148+
or file.env_path.parts[0].endswith(".data")
149+
)
135150

136151

137152
def install_data_file(env_path: pathlib.Path, file: EnvFile) -> None:
138153
if (
139-
len(file.site_packages_path.parts) > 2
140-
and file.site_packages_path.parts[1] == "scripts"
154+
len(file.env_path.parts) > 2
155+
and file.env_path.parts[1] == "scripts"
141156
):
142157
install_included_script(env_path, file.path)
158+
elif file.type_ == EnvPathType.DATA:
159+
install_site_file(env_path, file)
143160

144161

145162
def install_site_file(site_packages_path: pathlib.Path, file: EnvFile) -> None:
146-
site_path = site_packages_path / file.site_packages_path
163+
site_path = site_packages_path / file.env_path
147164
if not site_path.exists():
148165
site_path.parent.mkdir(parents=True, exist_ok=True)
149166
site_path.symlink_to(file.path.resolve())

example/BUILD.bazel

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@
1515
package(default_visibility = ["//visibility:public"])
1616

1717
load("@rules_pyvenv//:venv.bzl", "py_venv")
18-
load("@example_deps//:requirements.bzl", "requirement")
18+
load("@example_deps//:requirements.bzl", "requirement", "data_requirement")
1919
load("@rules_python//python/pip_install:requirements.bzl", "compile_pip_requirements")
2020

2121
py_venv(
2222
name = "venv",
2323
deps = [
24-
"//libraries/liba",
24+
"//libraries/liba",
2525
requirement("black"),
2626
requirement("numpy"),
2727
],
28+
data = [
29+
data_requirement("black"),
30+
],
2831
extra_pip_commands = [
2932
# "install ipython jupyter-console",
3033
]

example/WORKSPACE

+6-4
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
1919
# Required rules_python
2020
http_archive(
2121
name = "rules_python",
22-
sha256 = "8c8fe44ef0a9afc256d1e75ad5f448bb59b81aba149b8958f02f7b3a98f5d9b4",
23-
strip_prefix = "rules_python-0.13.0",
24-
url = "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.13.0.tar.gz",
22+
sha256 = "0a8003b044294d7840ac7d9d73eef05d6ceb682d7516781a4ec62eeb34702578",
23+
strip_prefix = "rules_python-0.24.0",
24+
url = "https://github.com/bazelbuild/rules_python/releases/download/0.24.0/rules_python-0.24.0.tar.gz",
2525
)
2626

27-
load("@rules_python//python:repositories.bzl", "python_register_toolchains")
27+
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
28+
29+
py_repositories()
2830

2931
python_register_toolchains(
3032
name = "python3_10",

example/requirements.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#
2-
# This file is autogenerated by pip-compile with python 3.10
3-
# To update, run:
2+
# This file is autogenerated by pip-compile with Python 3.10
3+
# by the following command:
44
#
55
# bazel run //:requirements.update
66
#
@@ -26,7 +26,7 @@ black==22.10.0 \
2626
--hash=sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4 \
2727
--hash=sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1 \
2828
--hash=sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff
29-
# via -r ./requirements.in
29+
# via -r requirements.in
3030
click==8.1.3 \
3131
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
3232
--hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
@@ -64,7 +64,7 @@ numpy==1.23.3 \
6464
--hash=sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e \
6565
--hash=sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85 \
6666
--hash=sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54
67-
# via -r ./requirements.in
67+
# via -r requirements.in
6868
pathspec==0.10.1 \
6969
--hash=sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93 \
7070
--hash=sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d

venv.bzl

+7-3
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ def _py_venv_deps_impl(ctx):
2727
imports.extend([i for i in dep[PyInfo].imports.to_list() if i not in imports])
2828

2929
deps = depset(transitive = [dep[DefaultInfo].default_runfiles.files for dep in ctx.attr.deps])
30+
data = depset(transitive = [data[DefaultInfo].files for data in ctx.attr.data])
3031
out = ctx.outputs.output
3132

3233
files = []
33-
for dep in deps.to_list():
34+
for dep in deps.to_list() + data.to_list():
3435
# Skip files that are provided by the python toolchain.
3536
# They don't need to be in the venv.
3637
if dep in toolchain_files:
@@ -54,15 +55,17 @@ _py_venv_deps = rule(
5455
implementation = _py_venv_deps_impl,
5556
attrs = {
5657
"deps": attr.label_list(),
58+
"data": attr.label_list(),
5759
"commands": attr.string_list(),
5860
"always_link": attr.bool(),
5961
"output": attr.output(),
6062
},
6163
toolchains = [PYTHON_TOOLCHAIN_TYPE],
6264
)
6365

64-
def py_venv(name, deps = None, extra_pip_commands = None, always_link = False, venv_location = None, **kwargs):
66+
def py_venv(name, deps = None, data = None, extra_pip_commands = None, always_link = False, venv_location = None, **kwargs):
6567
deps = deps or []
68+
data = data or []
6669
extra_pip_commands = extra_pip_commands or []
6770

6871
deps_name = "_" + name + "_deps"
@@ -71,6 +74,7 @@ def py_venv(name, deps = None, extra_pip_commands = None, always_link = False, v
7174
_py_venv_deps(
7275
name = deps_name,
7376
deps = deps,
77+
data = data,
7478
commands = extra_pip_commands,
7579
always_link = always_link,
7680
output = out_name,
@@ -88,7 +92,7 @@ def py_venv(name, deps = None, extra_pip_commands = None, always_link = False, v
8892
name = name,
8993
srcs = ["@rules_pyvenv//:build_env.py"],
9094
deps = ["@rules_pyvenv//vendor/importlib_metadata"],
91-
data = [out_label] + deps,
95+
data = [out_label] + deps + data,
9296
main = "@rules_pyvenv//:build_env.py",
9397
env = env,
9498
**kwargs,

0 commit comments

Comments
 (0)