Expose resolved .whl files in filegroup target of pypi__ packages. (#364)
diff --git a/python/pip_install/README.md b/python/pip_install/README.md
index c21dda0..4db1852 100644
--- a/python/pip_install/README.md
+++ b/python/pip_install/README.md
@@ -64,18 +64,27 @@
#### Example `BUILD` file.
```python
-load("@py_deps//:requirements.bzl", "requirement")
+load("@py_deps//:requirements.bzl", "requirement", "whl_requirement")
py_binary(
name = "main",
srcs = ["main.py"],
deps = [
requirement("boto3"),
- ],
+ ]
+)
+
+# If you need to depend on the wheel dists themselves, for instance to pass them
+# to some other packaging tool, you can get a handle to them with the whl_requirement macro.
+filegroup(
+ name = "whl_files",
+ data = [
+ whl_requirement("boto3"),
+ ]
)
```
-Note that above you do not need to add transitively required packages to `deps = [ ... ]`
+Note that above you do not need to add transitively required packages to `deps = [ ... ]` or `data = [ ... ]`
#### Setup `requirements.txt`
diff --git a/python/pip_install/extract_wheels/lib/BUILD b/python/pip_install/extract_wheels/lib/BUILD
index 001e333..4493bd1 100644
--- a/python/pip_install/extract_wheels/lib/BUILD
+++ b/python/pip_install/extract_wheels/lib/BUILD
@@ -41,6 +41,19 @@
],
)
+py_test(
+ name = "whl_filegroup_test",
+ size = "small",
+ srcs = [
+ "whl_filegroup_test.py",
+ ],
+ tags = ["unit"],
+ deps = [
+ ":lib",
+ ],
+ data = ["//experimental/examples/wheel:minimal_with_py_package"]
+)
+
filegroup(
name = "distribution",
srcs = glob(
diff --git a/python/pip_install/extract_wheels/lib/bazel.py b/python/pip_install/extract_wheels/lib/bazel.py
index 978f3a9..057c546 100644
--- a/python/pip_install/extract_wheels/lib/bazel.py
+++ b/python/pip_install/extract_wheels/lib/bazel.py
@@ -3,18 +3,22 @@
import textwrap
import json
from typing import Iterable, List, Dict, Set
+import shutil
from python.pip_install.extract_wheels.lib import namespace_pkgs, wheel, purelib
+WHEEL_FILE_LABEL = "whl"
+
def generate_build_file_contents(
- name: str, dependencies: List[str], pip_data_exclude: List[str]
+ name: str, dependencies: List[str], whl_file_deps: List[str], pip_data_exclude: List[str],
) -> str:
"""Generate a BUILD file for an unzipped Wheel
Args:
name: the target name of the py_library
dependencies: a list of Bazel labels pointing to dependencies of the library
+ whl_file_deps: a list of Bazel labels pointing to wheel file dependencies of this wheel.
Returns:
A complete BUILD file as a string
@@ -23,7 +27,7 @@
there may be no Python sources whatsoever (e.g. packages written in Cython: like `pymssql`).
"""
- data_exclude = ["**/*.py", "**/* *", "BUILD", "WORKSPACE"] + pip_data_exclude
+ data_exclude = ["*.whl", "**/*.py", "**/* *", "BUILD", "WORKSPACE"] + pip_data_exclude
return textwrap.dedent(
"""\
@@ -31,6 +35,12 @@
load("@rules_python//python:defs.bzl", "py_library")
+ filegroup(
+ name="{whl_file_label}",
+ srcs=glob(["*.whl"]),
+ data=[{whl_file_deps}]
+ )
+
py_library(
name = "{name}",
srcs = glob(["**/*.py"], allow_empty = True),
@@ -44,6 +54,8 @@
name=name,
dependencies=",".join(dependencies),
data_exclude=json.dumps(data_exclude),
+ whl_file_label=WHEEL_FILE_LABEL,
+ whl_file_deps=",".join(whl_file_deps),
)
)
@@ -70,6 +82,9 @@
def requirement(name):
name_key = name.replace("-", "_").replace(".", "_").lower()
return "{repo}//pypi__" + name_key
+
+ def whl_requirement(name):
+ return requirement(name) + ":whl"
""".format(
repo=repo_name, requirement_labels=",".join(sorted(targets))
)
@@ -125,7 +140,7 @@
pip_data_exclude: List[str],
enable_implicit_namespace_pkgs: bool,
) -> str:
- """Extracts wheel into given directory and creates a py_library target.
+ """Extracts wheel into given directory and creates py_library and filegroup targets.
Args:
wheel_file: the filepath of the .whl
@@ -141,6 +156,8 @@
directory = sanitise_name(whl.name)
os.mkdir(directory)
+ # copy the original wheel
+ shutil.copy(whl.path, directory)
whl.unzip(directory)
# Note: Order of operations matters here
@@ -150,14 +167,18 @@
setup_namespace_pkg_compatibility(directory)
extras_requested = extras[whl.name] if whl.name in extras else set()
+ whl_deps = sorted(whl.dependencies(extras_requested))
sanitised_dependencies = [
- '"//%s"' % sanitise_name(d) for d in sorted(whl.dependencies(extras_requested))
+ '"//%s"' % sanitise_name(d) for d in whl_deps
+ ]
+ sanitised_wheel_file_dependencies = [
+ '"//%s:%s"' % (sanitise_name(d), WHEEL_FILE_LABEL) for d in whl_deps
]
with open(os.path.join(directory, "BUILD"), "w") as build_file:
contents = generate_build_file_contents(
- sanitise_name(whl.name), sanitised_dependencies, pip_data_exclude,
+ sanitise_name(whl.name), sanitised_dependencies, sanitised_wheel_file_dependencies, pip_data_exclude
)
build_file.write(contents)
diff --git a/python/pip_install/extract_wheels/lib/whl_filegroup_test.py b/python/pip_install/extract_wheels/lib/whl_filegroup_test.py
new file mode 100644
index 0000000..39589c1
--- /dev/null
+++ b/python/pip_install/extract_wheels/lib/whl_filegroup_test.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+
+from python.pip_install.extract_wheels.lib import bazel
+
+
+class TestExtractWheel(unittest.TestCase):
+ def test_generated_build_file_has_filegroup_target(self) -> None:
+ wheel_name = "example_minimal_package-0.0.1-py3-none-any.whl"
+ wheel_dir = "experimental/examples/wheel/"
+ wheel_path = wheel_dir + wheel_name
+ generated_bazel_dir = bazel.extract_wheel(
+ wheel_path,
+ extras={},
+ pip_data_exclude=[],
+ enable_implicit_namespace_pkgs=False,
+ )[2:] # Take off the leading // from the returned label.
+ # Assert that the raw wheel ends up in the package.
+ self.assertIn(wheel_name, os.listdir(generated_bazel_dir))
+ with open("{}/BUILD".format(generated_bazel_dir)) as build_file:
+ build_file_content = build_file.read()
+ self.assertIn('filegroup', build_file_content)
+
+
+if __name__ == "__main__":
+ unittest.main()