Skip to content

Commit b981944

Browse files
Merge pull request #26 from dillon-giacoppo/dillon-format-and-lint
2 parents 14e0d35 + cebc3da commit b981944

23 files changed

+598
-390
lines changed

.bazelrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build --aspects @mypy_integration//:mypy.bzl%mypy_aspect
2+
build --output_groups=+mypy

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ dist/
2121
downloads/
2222
eggs/
2323
.eggs/
24-
lib/
2524
lib64/
2625
parts/
2726
sdist/

README.md

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
# rules_python_external
22

3-
Contains Bazel rules to fetch and install Python dependencies from a requirements.txt file.
3+
Bazel rules to transitively fetch and install Python dependencies from a requirements.txt file.
4+
5+
## Features
6+
7+
The rules address most of the top packaging issues in rules_python. This means the rules support common packages such
8+
as tensorflow and google.cloud natively.
9+
10+
* Transitive dependency resolution:
11+
[#35](https://github.com/bazelbuild/rules_python/issues/35),
12+
[#102](https://github.com/bazelbuild/rules_python/issues/102)
13+
* Minimal runtime dependencies:
14+
[#184](https://github.com/bazelbuild/rules_python/issues/184)
15+
* Support for [spreading purelibs](https://www.python.org/dev/peps/pep-0491/#installing-a-wheel-distribution-1-0-py32-none-any-whl):
16+
[#71](https://github.com/bazelbuild/rules_python/issues/71)
17+
* Support for [namespace packages](https://packaging.python.org/guides/packaging-namespace-packages/):
18+
[#14](https://github.com/bazelbuild/rules_python/issues/14),
19+
[#55](https://github.com/bazelbuild/rules_python/issues/55),
20+
[#65](https://github.com/bazelbuild/rules_python/issues/65),
21+
[#93](https://github.com/bazelbuild/rules_python/issues/93),
22+
[#189](https://github.com/bazelbuild/rules_python/issues/189)
23+
* Fetches pip packages only for building Python targets:
24+
[#96](https://github.com/bazelbuild/rules_python/issues/96)
25+
* Reproducible builds:
26+
[#154](https://github.com/bazelbuild/rules_python/issues/154),
27+
[#176](https://github.com/bazelbuild/rules_python/issues/176)
428

529
## Usage
630

7-
#### Setup `requirements.txt`
31+
#### Prerequisites
832

9-
While `rules_python_external` **does not** require a _transitively-closed_ `requirements.txt` file, it is recommended. But if you want to just have top-level packages listed, that works.
10-
11-
Transitively-closed requirements specs are very tedious to produce and maintain manually. To automate the process we recommend [`pip-compile` from `jazzband/pip-tools`](https://github.com/jazzband/pip-tools#example-usage-for-pip-compile).
12-
13-
For example, `pip-compile` takes a `requirements.in` like this:
14-
15-
```
16-
boto3~=1.9.227
17-
botocore~=1.12.247
18-
click~=7.0
19-
```
20-
21-
These above are the third-party packages you can directly import.
22-
23-
`pip-compile` 'compiles' it so you get a transitively-closed `requirements.txt` like this, which should be passed to `pip_install` below:
24-
25-
```
26-
boto3==1.9.253
27-
botocore==1.12.253
28-
click==7.0
29-
docutils==0.15.2 # via botocore
30-
jmespath==0.9.4 # via boto3, botocore
31-
python-dateutil==2.8.1 # via botocore
32-
s3transfer==0.2.1 # via boto3
33-
six==1.14.0 # via python-dateutil
34-
urllib3==1.25.8 # via botocore
35-
```
33+
The rules support Python >= 3.5 (the oldest [maintained release](https://devguide.python.org/#status-of-python-branches)).
3634

3735
#### Setup `WORKSPACE`
3836

@@ -57,7 +55,7 @@ pip_install(
5755
)
5856
```
5957

60-
Example `BUILD` file.
58+
#### Example `BUILD` file.
6159

6260
```python
6361
load("@py_deps//:requirements.bzl", "requirement")
@@ -66,13 +64,48 @@ py_binary(
6664
name = "main",
6765
srcs = ["main.py"],
6866
deps = [
69-
requirement("boto3"), # or @py_deps//pypi__boto3
67+
requirement("boto3"),
7068
],
7169
)
7270
```
7371

7472
Note that above you do not need to add transitively required packages to `deps = [ ... ]`
7573

74+
#### Setup `requirements.txt`
75+
76+
While `rules_python_external` **does not** require a _transitively-closed_ `requirements.txt` file, it is recommended.
77+
But if you want to just have top-level packages listed, that also will work.
78+
79+
Transitively-closed requirements specs are very tedious to produce and maintain manually. To automate the process we
80+
recommend [`pip-compile` from `jazzband/pip-tools`](https://github.com/jazzband/pip-tools#example-usage-for-pip-compile).
81+
82+
For example, `pip-compile` takes a `requirements.in` like this:
83+
84+
```
85+
boto3~=1.9.227
86+
botocore~=1.12.247
87+
click~=7.0
88+
```
89+
90+
`pip-compile` 'compiles' it so you get a transitively-closed `requirements.txt` like this, which should be passed to
91+
`pip_install` below:
92+
93+
```
94+
boto3==1.9.253
95+
botocore==1.12.253
96+
click==7.0
97+
docutils==0.15.2 # via botocore
98+
jmespath==0.9.4 # via boto3, botocore
99+
python-dateutil==2.8.1 # via botocore
100+
s3transfer==0.2.1 # via boto3
101+
six==1.14.0 # via python-dateutil
102+
urllib3==1.25.8 # via botocore
103+
```
104+
105+
### Demo
106+
107+
You can find a demo in the [example/](./example) directory.
108+
76109
## Development
77110

78111
### Testing

WORKSPACE

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,40 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
44

55
http_archive(
66
name = "rules_python",
7-
url = "https://github.com/bazelbuild/rules_python/releases/download/0.0.1/rules_python-0.0.1.tar.gz",
8-
sha256 = "aa96a691d3a8177f3215b14b0edc9641787abaaa30363a080165d06ab65e1161",
7+
sha256 = "d2865e2ce23ee217aaa408ddaa024ca472114a6f250b46159d27de05530c75e3",
8+
strip_prefix = "rules_python-7b222cfdb4e59b9fd2a609e1fbb233e94fdcde7c",
9+
url = "https://github.com/bazelbuild/rules_python/archive/7b222cfdb4e59b9fd2a609e1fbb233e94fdcde7c.tar.gz",
910
)
1011

1112
load("@rules_python//python:repositories.bzl", "py_repositories")
1213
py_repositories()
1314

1415
load("//:repositories.bzl", "rules_python_external_dependencies")
1516
rules_python_external_dependencies()
17+
18+
mypy_integration_version = "0.0.7" # latest @ Feb 10th 2020
19+
20+
http_archive(
21+
name = "mypy_integration",
22+
sha256 = "bf7ecd386740328f96c343dca095a63b93df7f86f8d3e1e2e6ff46e400880077", # for 0.0.7
23+
strip_prefix = "bazel-mypy-integration-{version}".format(version = mypy_integration_version),
24+
url = "https://github.com/thundergolfer/bazel-mypy-integration/archive/{version}.zip".format(
25+
version = mypy_integration_version
26+
),
27+
)
28+
29+
load(
30+
"@mypy_integration//repositories:repositories.bzl",
31+
mypy_integration_repositories = "repositories",
32+
)
33+
34+
mypy_integration_repositories()
35+
36+
load("@mypy_integration//:config.bzl", "mypy_configuration")
37+
mypy_configuration("//tools/typing:mypy.ini")
38+
39+
load("@mypy_integration//repositories:deps.bzl", mypy_integration_deps = "deps")
40+
mypy_integration_deps("//tools/typing:mypy_version.txt")
41+
42+
load("@mypy_integration//repositories:pip_repositories.bzl", "pip_deps")
43+
pip_deps()

defs.bzl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ def _pip_repository_impl(rctx):
2121
result = rctx.execute(
2222
[
2323
rctx.which(rctx.attr.python_interpreter),
24-
rctx.path(rctx.attr._script).dirname,
24+
"-m",
25+
"extract_wheels",
2526
"--requirements",
2627
rctx.path(rctx.attr.requirements),
2728
"--repo",
@@ -46,9 +47,6 @@ pip_repository = repository_rule(
4647
"python_interpreter": attr.string(default="python3"),
4748
# 600 is documented as default here: https://docs.bazel.build/versions/master/skylark/lib/repository_ctx.html#execute
4849
"timeout": attr.int(default = 600),
49-
"_script": attr.label(
50-
executable=True, default=Label("//src:__main__.py"), cfg="host",
51-
),
5250
},
5351
implementation=_pip_repository_impl,
5452
)

extract_wheels/BUILD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
load("//:repositories.bzl", "all_requirements")
2+
3+
py_binary(
4+
name = "extract_wheels",
5+
srcs = ["__init__.py", "__main__.py"],
6+
main = "__main__.py",
7+
deps = ["//extract_wheels/lib"],
8+
)

extract_wheels/__init__.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""extract_wheels
2+
3+
extract_wheels resolves and fetches artifacts transitively from the Python Package Index (PyPI) based on a
4+
requirements.txt. It generates the required BUILD files to consume these packages as Python libraries.
5+
6+
Under the hood, it depends on the `pip wheel` command to do resolution, download, and compilation into wheels.
7+
"""
8+
import argparse
9+
import glob
10+
import os
11+
import subprocess
12+
import sys
13+
14+
from extract_wheels.lib import wheel, bazel
15+
16+
17+
def configure_reproducible_wheels() -> None:
18+
"""Modifies the environment to make wheel building reproducible.
19+
20+
Wheels created from sdists are not reproducible by default. We can however workaround this by
21+
patching in some configuration with environment variables.
22+
"""
23+
24+
# wheel, by default, enables debug symbols in GCC. This incidentally captures the build path in the .so file
25+
# We can override this behavior by disabling debug symbols entirely.
26+
# https://github.com/pypa/pip/issues/6505
27+
if "CFLAGS" in os.environ:
28+
os.environ["CFLAGS"] += " -g0"
29+
else:
30+
os.environ["CFLAGS"] = "-g0"
31+
32+
# set SOURCE_DATE_EPOCH to 1980 so that we can use python wheels
33+
# https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/python.section.md#python-setuppy-bdist_wheel-cannot-create-whl
34+
if "SOURCE_DATE_EPOCH" not in os.environ:
35+
os.environ["SOURCE_DATE_EPOCH"] = "315532800"
36+
37+
# Python wheel metadata files can be unstable.
38+
# See https://bitbucket.org/pypa/wheel/pull-requests/74/make-the-output-of-metadata-files/diff
39+
if "PYTHONHASHSEED" not in os.environ:
40+
os.environ["PYTHONHASHSEED"] = "0"
41+
42+
43+
def main() -> None:
44+
"""Main program.
45+
46+
Exits zero on successful program termination, non-zero otherwise.
47+
"""
48+
49+
configure_reproducible_wheels()
50+
51+
parser = argparse.ArgumentParser(
52+
description="Resolve and fetch artifacts transitively from PyPI"
53+
)
54+
parser.add_argument(
55+
"--requirements",
56+
action="store",
57+
required=True,
58+
help="Path to requirements.txt from where to install dependencies",
59+
)
60+
parser.add_argument(
61+
"--repo",
62+
action="store",
63+
required=True,
64+
help="The external repo name to install dependencies. In the format '@{REPO_NAME}'",
65+
)
66+
args = parser.parse_args()
67+
68+
# Assumes any errors are logged by pip so do nothing. This command will fail if pip fails
69+
subprocess.check_output(
70+
[sys.executable, "-m", "pip", "wheel", "-r", args.requirements]
71+
)
72+
73+
targets = [
74+
'"%s%s"' % (args.repo, wheel.extract_wheel(whl, []))
75+
for whl in glob.glob("*.whl")
76+
]
77+
78+
with open("requirements.bzl", "w") as requirement_file:
79+
requirement_file.write(
80+
bazel.generate_requirements_file_contents(args.repo, targets)
81+
)

extract_wheels/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Main entry point."""
2+
import extract_wheels
3+
4+
if __name__ == "__main__":
5+
extract_wheels.main()

extract_wheels/lib/BUILD

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
load("//:repositories.bzl", "requirement")
2+
3+
py_library(
4+
name = "lib",
5+
visibility = ["//extract_wheels:__subpackages__"],
6+
srcs = [
7+
"bazel.py",
8+
"namespace_pkgs.py",
9+
"purelib.py",
10+
"wheel.py",
11+
],
12+
deps = [
13+
requirement("pkginfo"),
14+
requirement("setuptools"),
15+
],
16+
)
17+
18+
py_test(
19+
name = "namespace_pkgs_test",
20+
size = "small",
21+
srcs = [
22+
"namespace_pkgs_test.py",
23+
],
24+
tags = ["unit"],
25+
deps = [
26+
":lib",
27+
],
28+
)

0 commit comments

Comments
 (0)