Skip to content

Commit 6681d50

Browse files
committed
[bazel] Introduce a "py_import" rule, analogous to "java_import"
This allows us to use wheels and other tar.gz third party libraries in our python code. This will prove useful.
1 parent 811e42d commit 6681d50

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

py/defs.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
load("//py:import.bzl", _py_import = "py_import")
2+
3+
py_import = _py_import

py/import.bzl

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
def _py_import_impl(ctx):
2+
# Unpack the file somewhere, and present as a python library. We need to
3+
# know all the files in the zip, and that's problematic. For now, we might
4+
# be able to get away with just creating and declaring the directory.
5+
6+
root = ctx.actions.declare_directory("%s-pyroot" % ctx.attr.name)
7+
args = ctx.actions.args()
8+
9+
if ctx.file.wheel.path.endswith(".zip") or ctx.file.wheel.path.endswith(".whl"):
10+
args.add("x")
11+
args.add(ctx.file.wheel.path)
12+
args.add_all(["-d", root.path])
13+
14+
ctx.actions.run(
15+
outputs = [root],
16+
inputs = [ctx.file.wheel],
17+
arguments = [args],
18+
executable = ctx.executable._zip,
19+
)
20+
elif ctx.file.wheel.path.endswith(".tar.gz"):
21+
args.add(ctx.file.wheel.path)
22+
args.add(root.path)
23+
24+
ctx.actions.run(
25+
outputs = [root],
26+
inputs = [ctx.file.wheel],
27+
arguments = [args],
28+
executable = ctx.executable._untar,
29+
)
30+
else:
31+
fail("Unrecognised file extension: %s" % ctx.attr.wheel)
32+
33+
runfiles = ctx.runfiles(files = [root])
34+
for dep in ctx.attr.deps:
35+
runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
36+
37+
imports = depset(
38+
items = [
39+
"%s/%s/%s-pyroot" % (ctx.workspace_name, ctx.label.package, ctx.label.name),
40+
],
41+
transitive = [dep[PyInfo].imports for dep in ctx.attr.deps],
42+
)
43+
transitive_sources = depset(
44+
items = [],
45+
transitive = [dep[PyInfo].transitive_sources for dep in ctx.attr.deps],
46+
)
47+
48+
py_srcs = ctx.attr.srcs_version
49+
50+
info = PyInfo(
51+
imports = imports,
52+
has_py2_only_sources = py_srcs == "PY2",
53+
has_py3_only_sources = py_srcs == "PY3",
54+
transitive_sources = transitive_sources,
55+
uses_shared_libraries = not ctx.attr.zip_safe,
56+
)
57+
58+
return [
59+
DefaultInfo(
60+
files = depset(items = [root]),
61+
default_runfiles = runfiles,
62+
),
63+
info,
64+
]
65+
66+
py_import = rule(
67+
_py_import_impl,
68+
attrs = {
69+
"wheel": attr.label(
70+
allow_single_file = True,
71+
mandatory = True,
72+
),
73+
"zip_safe": attr.bool(
74+
default = True,
75+
),
76+
"python_version": attr.string(
77+
default = "PY3",
78+
values = ["PY2", "PY3"],
79+
),
80+
"srcs_version": attr.string(
81+
default = "PY2AND3",
82+
values = ["PY2", "PY3", "PY2AND3"],
83+
),
84+
"deps": attr.label_list(
85+
allow_empty = True,
86+
providers = [PyInfo],
87+
),
88+
"_zip": attr.label(
89+
allow_single_file = True,
90+
cfg = "host",
91+
default = "@bazel_tools//tools/zip:zipper",
92+
executable = True,
93+
),
94+
"_untar": attr.label(
95+
cfg = "exec",
96+
default = "//py:untar",
97+
executable = True,
98+
),
99+
},
100+
)

py/untar.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import os
2+
import sys
3+
import tarfile
4+
5+
if __name__ == '__main__':
6+
outdir = sys.argv[2]
7+
if not os.path.exists(outdir):
8+
os.makedirs(outdir)
9+
10+
tar = tarfile.open(sys.argv[1])
11+
for member in tar.getmembers():
12+
parts = member.name.split("/")
13+
parts.pop(0)
14+
if not len(parts):
15+
continue
16+
17+
basepath = os.path.join(*parts)
18+
basepath = os.path.normpath(basepath)
19+
member.name = basepath
20+
21+
dir = os.path.join(outdir, os.path.dirname(basepath))
22+
if not os.path.exists(dir):
23+
os.makedirs(dir)
24+
25+
tar.extract(member, outdir)
26+
tar.close()

0 commit comments

Comments
 (0)