Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add rule for bindgen (#102) #108

Merged
merged 30 commits into from
Feb 6, 2019
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f096c8c
Update bindgen.bzl
mfarrugi Jan 27, 2019
566f2fe
Get bindgen rule working locally.
mfarrugi Jan 27, 2019
ebca9c3
Add a test, too.
mfarrugi Jan 27, 2019
a120a99
Make the test a teensy bit less simple.
mfarrugi Jan 27, 2019
bf76c50
raze
mfarrugi Jan 27, 2019
40e5d52
Use raze (with hacks) for getting the bindgen binary.
mfarrugi Jan 27, 2019
7209c28
Make rustfmt optional and tidy it all up.
mfarrugi Jan 27, 2019
8054608
add note
mfarrugi Jan 27, 2019
ecb07c0
buildifier
mfarrugi Jan 27, 2019
c1baedb
Add docs and move stuff around.
mfarrugi Jan 27, 2019
725501c
tidy more
mfarrugi Jan 27, 2019
5070b0a
var renames
mfarrugi Jan 27, 2019
65c10d4
Add ubuntu 18.04 to .bazelci
mfarrugi Jan 27, 2019
2534cd5
Only run bindgen examples on ubuntu 18.04
mfarrugi Jan 27, 2019
4bbcb7a
revert tar path change
mfarrugi Feb 3, 2019
f558d5e
Move override for libloading out of generated crates.bzl
mfarrugi Feb 3, 2019
651627f
Copyrights to 2019.
mfarrugi Feb 3, 2019
e824e0d
Have macro forward kwargs to rust_library instead of rust_bindgen.
mfarrugi Feb 3, 2019
45592dd
Consolidate 'remove libstdc++ dep' todos.
mfarrugi Feb 3, 2019
485becc
Rename @clang to @bindgen_clang
mfarrugi Feb 3, 2019
25c3ca9
Add more broken stubs for stardoc...
mfarrugi Feb 3, 2019
11616c3
Commit hackily generated rust_bindgen_toolchain doc ...
mfarrugi Feb 3, 2019
243aa62
Fix commented out lines.
mfarrugi Feb 3, 2019
dfe5ded
Refer to ourselves as @io_bazel_rules_rust in WORKSPACE to be a littl…
mfarrugi Feb 3, 2019
20fd058
Add bindgen overview doc, make minor adjustments to proto docs to match.
mfarrugi Feb 3, 2019
55f0075
Replace 'new_local_repository' for libstdc++ with something that shou…
mfarrugi Feb 3, 2019
6c0b35b
take 2
mfarrugi Feb 3, 2019
0e7c86e
Apply suggestions from code review
damienmg Feb 5, 2019
f1f91e3
Address last review comments.
mfarrugi Feb 5, 2019
22ab34a
fix typo
mfarrugi Feb 6, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
---
default_targets: &default_targets
- "--" # Allows negative patterns; hack for https://github.com/bazelbuild/continuous-integration/pull/245
- "..."
- "@docs//..."
- "@examples//..."
# Bindgen currently only has a working toolchain for 18.04
- "-@examples//ffi/rust_calling_c/simple/..."
platforms:
ubuntu1404:
build_targets: *default_targets
test_targets: *default_targets
ubuntu1604:
build_targets: *default_targets
test_targets: *default_targets
ubuntu1804:
build_targets: *default_targets
test_targets:
- "..."
- "@docs//..."
- "@examples//..."
macos:
osx_targets: &osx_targets
- "--" # Allows negative patterns; hack for https://github.com/bazelbuild/continuous-integration/pull/245
Expand All @@ -19,11 +28,12 @@ platforms:
# Skip tests for dylib support on osx, since we don't support it yet.
- "-@examples//ffi/rust_calling_c:matrix_dylib_test"
- "-@examples//ffi/rust_calling_c:matrix_dynamically_linked"
- "-@examples//ffi/rust_calling_c/simple/..."
build_targets: *osx_targets
test_targets: *osx_targets
rbe_ubuntu1604:
test_targets:
- "--"
- "--" # Allows negative patterns; hack for https://github.com/bazelbuild/continuous-integration/pull/245
- "//test/..."
- "@examples//..."
- "-//test/conflicting_deps:conflicting_deps_test"
Expand All @@ -32,3 +42,4 @@ platforms:
- "-@examples//fibonacci:fibonacci_doc_test"
- "-@examples//hello_lib:hello_lib_doc_test"
- "-//tools/runfiles:runfiles_doc_test"
- "-@examples//ffi/rust_calling_c/simple/..."
14 changes: 7 additions & 7 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,22 @@ http_archive(
)

# TODO: Move this to examples/WORKSPACE when recursive repositories are enabled.
load("//rust:repositories.bzl", "rust_repositories")

load("@io_bazel_rules_rust//rust:repositories.bzl", "rust_repositories")
rust_repositories()

new_git_repository(
name = "libc",
build_file = "//:libc.BUILD",
build_file = "@io_bazel_rules_rust//:libc.BUILD",
remote = "https://github.com/rust-lang/libc",
tag = "0.2.20",
)

load("//proto:repositories.bzl", "rust_proto_repositories")

load("@io_bazel_rules_rust//proto:repositories.bzl", "rust_proto_repositories")
rust_proto_repositories()

load("@io_bazel_rules_rust//bindgen:repositories.bzl", "rust_bindgen_repositories")
rust_bindgen_repositories()
mfarrugi marked this conversation as resolved.
Show resolved Hide resolved

# Stardoc and its dependencies
git_repository(
name = "io_bazel_skydoc",
Expand Down Expand Up @@ -71,6 +72,5 @@ http_archive(
],
)

load(":workspace.bzl", "bazel_version")

load("@io_bazel_rules_rust//:workspace.bzl", "bazel_version")
bazel_version(name = "bazel_version")
26 changes: 26 additions & 0 deletions bindgen/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load("@io_bazel_rules_rust//bindgen:bindgen.bzl", "rust_bindgen_toolchain")
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

package(default_visibility = ["//visibility:public"])

toolchain_type(name = "bindgen_toolchain")

bzl_library(
name = "rules",
srcs = glob(["**/*.bzl"]),
deps = ["@io_bazel_rules_rust//rust:rules"],
)

rust_bindgen_toolchain(
name = "example-bindgen-toolchain-impl",
bindgen = "//bindgen/raze:cargo_bin_bindgen",
clang = "@bindgen_clang//:clang",
libclang = "@bindgen_clang//:libclang.so",
libstdcxx = "@local_libstdcpp//:libstdc++",
)

toolchain(
name = "example-bindgen-toolchain",
toolchain = "example-bindgen-toolchain-impl",
toolchain_type = "@io_bazel_rules_rust//bindgen:bindgen_toolchain",
)
82 changes: 82 additions & 0 deletions bindgen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Rust Bindgen Rules

<div class="toc">
<h2>Rules</h2>
<ul>
<li><a href="docs/index.md#rust_bindgen_toolchain">rust_bindgen_toolchain</a></li>
<li>rust_bindgen</li>
<li>rust_bindgen_library</li>
</ul>
</div>

## Overview

These rules are for using [Bindgen][bindgen] to generate [Rust][rust] bindings to C (and some C++) ilbraries.
mfarrugi marked this conversation as resolved.
Show resolved Hide resolved

[rust]: http://www.rust-lang.org/
[bindgen]: https://github.com/rust-lang/rust-bindgen

See the [bindgen example](../examples/ffi/rust_calling_c/simple/BUILD#L9) for a more complete example of use.

### Setup

To use the Rust bindgen rules, add the following to your `WORKSPACE` file to add the
external repositories for the Rust bindgen toolchain (in addition to the [rust rules setup](..)):

```python
load("@io_bazel_rules_rust//bindgen:repositories.bzl", "rust_bindgen_repositories")

rust_bindgen_repositories()
```
This makes the default toolchain defined in [`@io_bazel_rules_rust`](./BUILD) available.

[raze]: https://github.com/google/cargo-raze

It will load crate dependencies of bindgen that are generated using
[cargo raze][raze] inside the rules_rust
repository. However, using those dependencies might conflict with other uses
of [cargo raze][raze]. If you need to change
those dependencies, please see the [dedicated section below](#custom-deps).

For additional information, see the [Bazel toolchains documentation](https://docs.bazel.build/versions/master/toolchains.html).

## <a name="custom-deps">Customizing dependencies

These rules depends on the [`bindgen`](https://crates.io/crates/bindgen) binary crate, and it
in turn depends on both a clang binary and the clang library. To obtain these dependencies,
`rust_bindgen_repositories` imports bindgen and its dependencies using BUILD files generated with
[`cargo raze`][raze], along with a tarball of clang.

If you want to change the bindgen used, or use [`cargo raze`][raze] in a more
complex scenario (with more dependencies), you must redefine those
dependencies.

To do this, once you've imported the needed dependencies (see our
[Cargo.toml](./raze/Cargo.toml) file to see the default dependencies), you
need to create your own toolchain. To do so you can create a BUILD
file with your [`rust_bindgen_toolchain`](../docs/index.md#rust_bindgen_toolchain) definition, for example:

```python
load("@io_bazel_rules_rust//bindgen:bindgen.bzl", "rust_bindgen_toolchain")

rust_bindgen_toolchain(
name = "bindgen-toolchain-impl",
bindgen = "//my/raze:cargo_bin_bindgen",
clang = "//my/clang:clang",
libclang = "//my/clang:libclang.so",
libstdcxx = "//my/cpp:libstdc++",
)

toolchain(
name = "bindgen-toolchain",
toolchain = "bindgen-toolchain-impl",
toolchain_type = "@io_bazel_rules_rust//bindgen:bindgen_toolchain",
)
```

Now that you have your own toolchain, you need to register it by
inserting the following statement in your `WORKSPACE` file:

```python
register_toolchains("//my/toolchains:bindgen-toolchain")
```
182 changes: 182 additions & 0 deletions bindgen/bindgen.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load("@io_bazel_rules_rust//rust:rust.bzl", "rust_library")

def rust_bindgen_library(
name,
header,
cc_lib,
bindgen_flags = None,
clang_flags = None,
**kwargs):
"""Generates a rust source file for `header`, and builds a rust_library.

Arguments are the same as `rust_bindgen`, and `kwargs` are passed directly to rust_library.
"""

rust_bindgen(
name = name + "__bindgen",
header = header,
cc_lib = cc_lib,
bindgen_flags = bindgen_flags or [],
clang_flags = clang_flags or [],
)
rust_library(
name = name,
srcs = [name + "__bindgen.rs"],
deps = [cc_lib],
**kwargs
)

def _rust_bindgen_impl(ctx):
# nb. We can't grab the cc_library`s direct headers, so a header must be provided.
cc_lib = ctx.attr.cc_lib
header = ctx.file.header
if header not in cc_lib.cc.transitive_headers:
fail("Header {} is not in {}'s transitive headers.".format(ctx.attr.header, cc_lib), "header")

toolchain = ctx.toolchains["@io_bazel_rules_rust//bindgen:bindgen_toolchain"]
bindgen_bin = toolchain.bindgen
rustfmt_bin = toolchain.rustfmt
clang_bin = toolchain.clang
libclang = toolchain.libclang

# TODO: This rule shouldn't need to depend on libstdc++
# This rule requires an explicit dependency on a libstdc++ because
# 1. It is a runtime dependency of libclang.so
# 2. We cannot locate it in the cc_toolchain yet
# Depending on how libclang.so was compiled, it may try to locate its libstdc++ dependency
# in a way that makes our handling here unnecessary (eg. system /usr/lib/x86_64-linux-gnu/libstdc++.so.6)
libstdcxx = toolchain.libstdcxx

# rustfmt is not where bindgen expects to find it, so we format manually
bindgen_args = ["--no-rustfmt-bindings"] + ctx.attr.bindgen_flags
clang_args = ctx.attr.clang_flags

output = ctx.outputs.out

# libclang should only have 1 output file
libclang_dir = libclang.cc.libs.to_list()[0].dirname
include_directories = depset(
[f.dirname for f in cc_lib.cc.transitive_headers],
)

# Vanilla usage of bindgen produces formatted output, here we do the same if we have `rustfmt` in our toolchain.
if rustfmt_bin:
unformatted_output = ctx.actions.declare_file(output.basename + ".unformatted")
else:
unformatted_output = output

args = ctx.actions.args()
args.add_all(bindgen_args)
args.add(header.path)
args.add("--output", unformatted_output.path)
args.add("--")
args.add_all(include_directories, before_each = "-I")
args.add_all(clang_args)
ctx.actions.run(
executable = bindgen_bin,
inputs = depset(
[header],
transitive = [cc_lib.cc.transitive_headers, libclang.cc.libs, libstdcxx.cc.libs],
),
outputs = [unformatted_output],
mnemonic = "RustBindgen",
progress_message = "Generating bindings for {}..".format(header.path),
env = {
"RUST_BACKTRACE": "1",
"CLANG_PATH": clang_bin.path,
# Bindgen loads libclang at runtime, which also needs libstdc++, so we setup LD_LIBRARY_PATH
"LIBCLANG_PATH": libclang_dir,
"LD_LIBRARY_PATH": ":".join([f.dirname for f in libstdcxx.cc.libs]),
},
arguments = [args],
tools = [clang_bin],
)

if rustfmt_bin:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is running rustfmt needed on generated sources? I meant, they are not made for being read anyway.

Copy link
Collaborator

@acmcarther acmcarther Jan 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've personally found it useful (in my own hacky version of this) when inspecting generated files. In my experience, bindgen can be a bit finnicky and require special params to configure generation. Its much easier to diagnose issues in a formatted file compared to a super long single line unformatted file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond being useful, this is what bindgen does by default. I think it is best to keep parity with other tools whenever possible.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, can you just add a comment about it?

ctx.actions.run_shell(
inputs = depset([rustfmt_bin, unformatted_output]),
outputs = [output],
command = "{} --emit stdout --quiet {} > {}".format(rustfmt_bin.path, unformatted_output.path, output.path),
damienmg marked this conversation as resolved.
Show resolved Hide resolved
tools = [rustfmt_bin],
)

rust_bindgen = rule(
_rust_bindgen_impl,
doc = "Generates a rust source file from a cc_library and a header.",
attrs = {
"header": attr.label(
doc = "The .h file to generate bindings for.",
allow_single_file = True,
),
"cc_lib": attr.label(
doc = "The cc_library that contains the .h file. This is used to find the transitive includes.",
providers = ["cc"],
),
"bindgen_flags": attr.string_list(
doc = "Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.",
),
"clang_flags": attr.string_list(
doc = "Flags to pass directly to the clang executable.",
),
},
outputs = {"out": "%{name}.rs"},
toolchains = [
"@io_bazel_rules_rust//bindgen:bindgen_toolchain",
],
)

def _rust_bindgen_toolchain_impl(ctx):
return platform_common.ToolchainInfo(
bindgen = ctx.executable.bindgen,
clang = ctx.executable.clang,
libclang = ctx.attr.libclang,
libstdcxx = ctx.attr.libstdcxx,
rustfmt = ctx.executable.rustfmt,
)

rust_bindgen_toolchain = rule(
_rust_bindgen_toolchain_impl,
doc = "The tools required for the `rust_bindgen` rule.",
attrs = {
"bindgen": attr.label(
doc = "The label of a `bindgen` executable.",
executable = True,
cfg = "host",
),
"rustfmt": attr.label(
doc = "The label of a `rustfmt` executable. If this is provided, generated sources will be formatted.",
executable = True,
cfg = "host",
mandatory = False,
),
"clang": attr.label(
doc = "The label of a `clang` executable.",
executable = True,
cfg = "host",
),
"libclang": attr.label(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised you did not got hit by bazelbuild/bazel#6889. This will get shipped in the host configuration :(

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if @mfarrugi fully follows the issue, but I'm a bit confused.

Isn't host configuration the configuration we want to run bindgen in?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to run this at build-time on the host, so I don't follow what "shipped" means. (I also don't understand the issue :))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to run this in host configuration please add cfg = "host". The default value of cfg is "target" but due to bazelbuild/bazel#6889 it somehow becomes the host configuration. This is a bug in Bazel.

doc = "A cc_library that provides bindgen's runtime dependency on libclang.",
cfg = "host",
providers = ["cc"],
),
"libstdcxx": attr.label(
doc = "A cc_library that satisfies libclang's libstdc++ dependency.",
cfg = "host",
providers = ["cc"],
),
},
)
14 changes: 14 additions & 0 deletions bindgen/clang.BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package(default_visibility = ["//visibility:public"])

sh_binary(
name = "clang",
srcs = ["bin/clang"],
data = glob(["lib/**"]),
)

cc_library(
name = "libclang.so",
srcs = [
"lib/libclang.so",
],
)
Loading