Skip to content

Commit

Permalink
add bazel wheel rule setup
Browse files Browse the repository at this point in the history
  • Loading branch information
georgeliaw committed Feb 21, 2018
1 parent e792be5 commit 1fa6eeb
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea
bazel-*
5 changes: 5 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
skylark_doc(
name = "wheel-docs",
srcs = ["//wheel:wheel.bzl"],
format = "markdown"
)
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,43 @@
# bazel_rules_wheel
Bazel rule for building a python wheel
# rules_wheel
Bazel rule for building a python wheel.

This aims to simplify the wheel-building process by taking over the need to write and maintain extra `setup.py` files and then building via `genrule`.
Instead, you can use this rule to wrap that whole process for you.

You can check out the Skydoc-generated wheel docs [here](docs/wheel.md).

Unfortunately Skydoc doesn't currently support the newer `doc` label parameter,
so check out the Skylark source in [wheel.bzl](wheel/wheel.bzl) for more info.

# Installing and Usage
Currently requires having `setuptools` installed locally (will work on bringing that into the rule itself).

To use the wheel rule, you will need to add the following into your `WORKSPACE` file:
```
http_archive(
name = "io_bazel_rules_wheel",
strip_prefix = "rules_wheel-<version>",
urls = ["https://github.com/georgeliaw/rules_wheel/archive/<version>.tar.gz"],
sha256 = "<checksum>"
)
```

To load the rules, either do so in your `BUILD` files or simply add to `tools/build_rules/prelude_bazel`:
```
load("@io_bazel_rules_wheel//wheel:wheel.bzl", "bdist_wheel")
```
NOTE: using `prelude_bazel` requires an empty `tools/build_rules/BUILD` file.

You can now create wheels by doing something similar to the below:
```
bdist_wheel(
name = "sample_wheel",
srcs = glob(
["**"],
exclude = ["**/*.pyc"],
),
data = {
'': ['**/*']
}
)
```
15 changes: 15 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
git_repository(
name = "io_bazel_rules_sass",
remote = "https://github.com/bazelbuild/rules_sass.git",
tag = "0.0.3",
)
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_repositories")
sass_repositories()

git_repository(
name = "io_bazel_skydoc",
remote = "https://github.com/bazelbuild/skydoc.git",
tag = "0.1.4",
)
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
skydoc_repositories()
84 changes: 84 additions & 0 deletions docs/wheel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

<!---
Documentation generated by Skydoc
-->
<h1>Bazel rule for building a python wheel</h1>


<nav class="toc">
<h2>Rules</h2>
<ul>
<li><a href="#bdist_wheel">bdist_wheel</a></li>
</ul>
</nav>
<a name="bdist_wheel"></a>

## bdist_wheel
<pre>
bdist_wheel(<a href="#bdist_wheel.name">name</a>, <a href="#bdist_wheel.data">data</a>, <a href="#bdist_wheel.srcs">srcs</a>, <a href="#bdist_wheel.classifiers">classifiers</a>, <a href="#bdist_wheel.description">description</a>, <a href="#bdist_wheel.install_requires">install_requires</a>, <a href="#bdist_wheel.platform">platform</a>, <a href="#bdist_wheel.version">version</a>)
</pre>




<a name="bdist_wheel_args"></a>
### Attributes


<table class="params-table">
<colgroup>
<col class="col-param" />
<col class="col-description" />
</colgroup>
<tbody>
<tr id="bdist_wheel.name">
<td><code>name</code></td>
<td>
<p><code><a href="https://bazel.build/docs/build-ref.html#name">Name</a>; Required</code></p>
<p>A unique name for this rule.</p>
</td>
</tr>
<tr id="bdist_wheel.data">
<td><code>data</code></td>
<td>
<p><code>Dictionary mapping strings to lists of strings; Optional; Default is {}</code></p>
</td>
</tr>
<tr id="bdist_wheel.srcs">
<td><code>srcs</code></td>
<td>
<p><code>List of <a href="https://bazel.build/docs/build-ref.html#labels">labels</a>; Required</code></p>
</td>
</tr>
<tr id="bdist_wheel.classifiers">
<td><code>classifiers</code></td>
<td>
<p><code>List of strings; Optional; Default is []</code></p>
</td>
</tr>
<tr id="bdist_wheel.description">
<td><code>description</code></td>
<td>
<p><code>String; Optional; Default is ''</code></p>
</td>
</tr>
<tr id="bdist_wheel.install_requires">
<td><code>install_requires</code></td>
<td>
<p><code>List of strings; Optional; Default is []</code></p>
</td>
</tr>
<tr id="bdist_wheel.platform">
<td><code>platform</code></td>
<td>
<p><code>List of strings; Optional; Default is ['any']</code></p>
</td>
</tr>
<tr id="bdist_wheel.version">
<td><code>version</code></td>
<td>
<p><code>String; Optional; Default is '0.0.1'</code></p>
</td>
</tr>
</tbody>
</table>
Empty file added tools/build_rules/BUILD
Empty file.
6 changes: 6 additions & 0 deletions tools/build_rules/prelude_bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
load(
"@io_bazel_skydoc//skylark:skylark.bzl",
"skydoc_repositories",
"skylark_library",
"skylark_doc",
)
1 change: 1 addition & 0 deletions wheel/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(["setup.py.template", "MANIFEST.in.template", "wheel.bzl"])
1 change: 1 addition & 0 deletions wheel/MANIFEST.in.template
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{manifest}
14 changes: 14 additions & 0 deletions wheel/setup.py.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env python
from setuptools import setup, find_packages

setup(
name="{name}",
version="{version}",
packages=find_packages(),
description="{description}",
classifiers={classifiers},
platforms={platforms},
package_data={package_data},
include_package_data={include_package_data},
install_requires={install_requires},
)
143 changes: 143 additions & 0 deletions wheel/wheel.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""Bazel rule for building a python wheel"""

def _generate_setup_py(ctx):
classifiers = ','.join(['"{}"'.format(i) for i in ctx.attr.classifiers])
install_requires = ','.join(['"{}"'.format(i) for i in ctx.attr.install_requires])
setup_py = ctx.actions.declare_file("setup.py")

# create setup.py
ctx.actions.expand_template(
template=ctx.file._setup_py_template,
output=setup_py,
substitutions={
"{name}": ctx.attr.name,
"{version}": ctx.attr.version,
"{description}": ctx.attr.description,
"{classifiers}": str(classifiers) or str([]),
"{platforms}": str(ctx.attr.platform),
"{package_data}": str(ctx.attr.data),
"{include_package_data}": str(ctx.attr.include_package_data),
"{install_requires}": str(install_requires) or str([])
}
)

return setup_py

def _generate_manifest(ctx, setup_py, package_name):
manifest_text = '\n'.join([i for i in ctx.attr.manifest]).format(package_name=package_name)

manifest = ctx.actions.declare_file("MANIFEST.in", sibling=setup_py)
ctx.actions.expand_template(
template=ctx.file._manifest_template,
output=manifest,
substitutions={
"{manifest}": manifest_text
}
)

return manifest

def _bdist_wheel_impl(ctx):
work_dir = "wheel"
build_file_dir = ctx.build_file_path.rstrip('/BUILD')
package_name = build_file_dir.split('/')[-1]
package_dir = '/'.join([ctx.genfiles_dir.path, work_dir, package_name])

setup_py_dest_dir = '/'.join([package_dir, '/'.join(build_file_dir.split('/')[:-1])])
setup_py_dest_dir_depth = len(setup_py_dest_dir.split('/'))
backtrack_path = '/'.join(['..' for i in range(0, setup_py_dest_dir_depth)])

setup_py = _generate_setup_py(ctx)
manifest = _generate_manifest(ctx, setup_py, package_name)

ctx.actions.run_shell(
mnemonic="BuildWheel",
outputs=[ctx.outputs.wheel],
inputs=ctx.files.srcs + [setup_py, manifest],
command="mkdir -p {package_dir} \
&& cp --parents -t {package_dir} {source_list} \
&& cp {setup_py_path} {setup_py_dest_dir} \
&& cp {manifest_path} {setup_py_dest_dir} \
&& chmod a+w {setup_py_dest_dir}/setup.py {setup_py_dest_dir}/MANIFEST.in \
&& cd {setup_py_dest_dir} \
&& ./setup.py bdist_wheel --bdist-dir {bdist_dir} --dist-dir {dist_dir} \
&& cd {backtrack_path} \
&& rm -rf {setup_py_dest_dir}".format(
source_list=' '.join([src.path for src in ctx.files.srcs]),
setup_py_path=ctx.outputs.setup_py.path,
manifest_path=ctx.outputs.manifest.path,
package_dir=package_dir,
setup_py_dest_dir=setup_py_dest_dir,
bdist_dir=package_dir + "/build",
dist_dir=backtrack_path + "/" + ctx.outputs.wheel.dirname,
backtrack_path=backtrack_path
)
)

return DefaultInfo(files=depset([ctx.outputs.wheel]))

_bdist_wheel_attrs = {
"srcs": attr.label_list(
doc='Source files to include in the wheel',
allow_files=[".py"],
mandatory=True,
allow_empty=False
),
"version": attr.string(
default='0.0.1',
doc='Version to be assigned to the wheel.',
mandatory=False
),
"description": attr.string(
doc='Short description of the wheel, no more than 200 characters.',
mandatory=False
),
"classifiers": attr.string_list(
doc='Classifiers for the wheel.',
mandatory=False
),
"platform": attr.string_list(
default=['any'],
doc='Platform the wheel is being built for.',
mandatory=False
),
"data": attr.string_list_dict(
doc='A dictionary that maps packages to lists of glob patterns of non-python files listed in `srcs` to include in the wheel.',
mandatory=False
),
"manifest": attr.string_list(
default=['recursive-include {package_name} *'],
doc='List of statements to insert into the MANIFEST.in file.',
mandatory=False
),
"include_package_data": attr.bool(
default=False,
doc='Whether to use the setuptools `include_package_data` setting. Note that if used with `data`, only data files specified in `manifest` will be included.',
mandatory=False
),
"install_requires": attr.string_list(
doc='A list of strings specifying what other wheels need to be installed when this one is.',
mandatory=False
),
"_setup_py_template": attr.label(
default=Label("//wheel:setup.py.template"),
allow_single_file=True
),
"_manifest_template": attr.label(
default=Label("//wheel:MANIFEST.in.template"),
allow_single_file=True
)
}

_bdist_wheel_outputs = {
"wheel": "%{name}-%{version}-py2-none-%{platform}.whl",
"setup_py": "setup.py",
"manifest": "MANIFEST.in"
}

bdist_wheel = rule(
implementation = _bdist_wheel_impl,
executable = False,
attrs = _bdist_wheel_attrs,
outputs = _bdist_wheel_outputs,
)

0 comments on commit 1fa6eeb

Please sign in to comment.