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

Added rule_cbindgen #392

Closed
wants to merge 6 commits into from
Closed

Added rule_cbindgen #392

wants to merge 6 commits into from

Conversation

UebelAndre
Copy link
Collaborator

@UebelAndre UebelAndre commented Sep 2, 2020

This pull request adds a rust_cbindgen rule for generating C/C++ bindings to Rust code. (see #381)

(I'll update the description of this pull-request once I'm able to figure out how to properly generate documentation)

@UebelAndre
Copy link
Collaborator Author

UebelAndre commented Sep 2, 2020

There are a few TODOs I could use some help with. I'm not quite sure how to go about doing these...

TODO:

  • Properly update/generate documentation.
  • Add examples of rust_cbindgen being used for other languages (swift/java)
  • Address clippy defects
  • Fix windows support

@UebelAndre
Copy link
Collaborator Author

@damienmg @mfarrugi @dfreese If one of you guys wouldn't mind taking a quick look at this PR and the comments above, that'd be super helpful 🙏

@damienmg
Copy link
Collaborator

damienmg commented Sep 2, 2020

Can you put the generated files in a separate commit?
Also the build is broken on windows (seems to be a dependency issue), maybe exclude those target from the windows build for now along with a TODO (obviously if you can make them work on windows that's better).

@UebelAndre
Copy link
Collaborator Author

@damienmg Yeah, I'll split up the commit. Also, do you know if documentation is generated or hand written in the /docs directory?

@damienmg
Copy link
Collaborator

damienmg commented Sep 2, 2020

For the documentation, you just have to run https://github.com/bazelbuild/rules_rust/blob/master/docs/update_docs.sh and that should just work (TM).

@UebelAndre
Copy link
Collaborator Author

I tried that but I'll try it again 😅

@UebelAndre
Copy link
Collaborator Author

UebelAndre commented Sep 7, 2020

Ok, some things I could use some help with:

  1. I'm not entirely sure why I'm getting Windows build errors. Applying this patch shut it up but I'd like to not have to do this:
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 7ae5fed..2517ac0 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -53,8 +53,6 @@ tasks:
     - "-@examples//ffi/rust_calling_c:matrix_dylib_test"
     - "-@examples//ffi/rust_calling_c:matrix_dynamically_linked"
     - "-@examples//ffi/rust_calling_c/simple/..."
+    # TODO: Remove
+    - "-@examples//cbindgen/..."
     build_targets: *windows_targets
     test_targets: *windows_targets
   examples:
  1. I have no idea what the clippy errors are or what is going on with this test. The errors are complaining about errors found in generated 'remote' targets from cargo raze. I don't think clippy should be ran on external repositories. Suggestions are greatly welcomed.
  2. If I run ./docs/update_docs.sh I get a pretty useless commit added to this PR. How do I generate docs based on all the docstrings and doc values in the new rust_cbindgen rule? I would have expected something like skydoc to do this but I've never used the tool before so my expectations may be flawed.

edit: I figured out how to generate docs. There should be a guide for this somewhere in this repo... (I may open a PR later)

@damienmg
Copy link
Collaborator

damienmg commented Sep 8, 2020

  1. I personally have no idea about the windows support, this has always been an issue for us as nobody is working on Windows. I am fine with just skipping the cbindgen test as we also skip the FFI tests.

  2. @smklein might have a better idea on that one, but how I read it is that it has nothing to do with clippy itself:

(22:45:40) ERROR: While resolving configuration keys for @cbindgen_example_4__clap__2_33_3//:clap: no such target '@io_bazel_rules_rust//rust/platform:wasm32-unknown-unknown': target 'wasm32-unknown-unknown' not declared in package 'rust/platform' defined by /var/lib/buildkite-agent/.cache/bazel/_bazel_buildkite-agent/90e2e9b2d72fa9b00c19ff0535bb31ae/external/io_bazel_rules_rust/rust/platform/BUILD

It seems like it select the target wasm32-unknown-unkown but that does not make sense to me.

@UebelAndre
Copy link
Collaborator Author

@damienmg Ah! I'm sorry, I had accidentally pushed changes to this branch I never intended to. I've updated my commits and the original error is back.

The issues related to wasm32-unknown-unknown are from some WIP stuff I was doing for #399

@UebelAndre
Copy link
Collaborator Author

My apologieze to anyone subscribed to this pull request in case all my commits spammed notifications somehow.

I fixed what I could for Windows. I wasn't able to get the example_2 test to work. Perhaps someone could take a look and make a recommendation (see last failing build). I tried setting up a Windows dev environment and that was also a huge headache 😢

All that being said though, I think this PR is ready for some review. I'll look into a java example later for cbindgen. I don't think swift is going to be all that easy for platforms that aren't MacOS.

@UebelAndre
Copy link
Collaborator Author

@damienmg Could I ask you to be my reviewer or recommend one? 😄

@damienmg
Copy link
Collaborator

damienmg commented Sep 9, 2020

Assigning Marc as he has more knowledge than me around C / rust interactions.

cbindgen/cbindgen.bzl Outdated Show resolved Hide resolved
cbindgen/cbindgen.bzl Outdated Show resolved Hide resolved
system_includes = rust_compilation_context.system_includes,
)

# Return all providers given by `rust_library` to ensure compatiblity with other rules
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need to do that? Should this say cc_library?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

rust_cbindgen_library should effectively be transparent to other rules, in my opinion. It's job is only to inject a header into the outputs of a rust_library. So I thought the providers returned by the rule should match a rust_library

Copy link
Collaborator

Choose a reason for hiding this comment

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

I still think it would be weird for another rust_library to depend on this.

It would make more sense if this was generated on-demand when a cc_library consumed a rust_library, but I have no idea how to do that. Maybe @damienmg knows something about that sort of thing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That actually sounds more ideal. All I want is to use a rust_library target with other targets that are compatible with cc_library. I do feel it's nicer to forward all the providers in this case though to avoid potential situations where people want the header file generated by this rule as well as other crate info. But yeah, I'd love to discuss a solution where rust_library just has a generated header.

Copy link
Collaborator Author

@UebelAndre UebelAndre Sep 10, 2020

Choose a reason for hiding this comment

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

I actually think this is worth getting more input on. @smklein @damienmg would you guys also be able to chime in on this? I think it would be fantastic if rust_library had a compatible header file such that it could be used in all places a cc_library was expected. I think this could simplify the implementation and be far more impactful and intuitive.

cbindgen/cbindgen.bzl Outdated Show resolved Hide resolved
cbindgen/cbindgen.bzl Outdated Show resolved Hide resolved
cbindgen/private/cbindgen.toml.template Outdated Show resolved Hide resolved
#[no_mangle]
pub extern "C" fn struct_into_rust(point: Point) {
let epsilon = 0.01f32;
assert!((point.x - 42.0f32).abs() < epsilon);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, shouldn't the result be bitwise identical?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is but clippy was complaining about float comparisons so I (likely did the bad thing and) changed the implementation. Any suggestions?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Contributor

@smklein smklein Sep 10, 2020

Choose a reason for hiding this comment

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

If this is for https://rust-lang.github.io/rust-clippy/master/#float_cmp , there are some examples of how to fix (without the annotation to skip the lint, but that is always an option too).

@UebelAndre , are you still running into issues with clippy beyond the examples directory?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, that was the only outstanding issue. I had some complaints about the use of unsafe and the float_cmp. I'll check those out, thanks guys 😄

@mfarrugi
Copy link
Collaborator

I fixed what I could for Windows. I wasn't able to get the example_2 test to work. Perhaps someone could take a look and make a recommendation (see last failing build). I tried setting up a Windows dev environment and that was also a huge headache 😢

It looks like the windows build doesn't pick up the rust .so, so potentially something wrong with our CcInfo.. I doubt it has anything to do with cbindgen.

Add examples of rust_cbindgen being used for other languages (swift/java)

Maybe python would be easier to do, but that's up to you.


How does this work with dependencies? eg. If cbindgen_example.rs depended on some other crates internally, is that fine? And if a dependency's type is exported to the c api (ie. assuming it is #repr(c) or an opaque type)?

@UebelAndre
Copy link
Collaborator Author

How does this work with dependencies?

I can update the examples to demonstrate that but anything that's doable with cbindgen I'd expect to "just work" within this rule so long as I'm compiling the binary correctly.

Maybe python would be easier to do, but that's up to you.

Yeah, I may try that, I don't think java will be too hard but am nervous about swift. I'm curious to know if I there would be a way to limit certain targets to certain platforms such that bazel build //... would only build what that platform is capable of building.

@UebelAndre
Copy link
Collaborator Author

@mfarrugi I also feel like the current state of the examples is not the greatest. I tried to reduce code duplication and feel like I ended up over-complicating them. Suggestions are welcome

@mfarrugi
Copy link
Collaborator

I'd expect to "just work" within this rule so long as I'm compiling the binary correctly.

I don't see how cbindgen knows how to parse past the current crate, but it seems to work with libc (although that could be special cased and use the rustc private version, or something like that).

@mfarrugi I also feel like the current state of the examples is not the greatest.

The only thing that comes to mind is pasting the .cpp code which might be easier to follow, but kind of obscures that the intent is to test different build parameter permutations.

@mfarrugi
Copy link
Collaborator

mfarrugi commented Sep 13, 2020 via email

@UebelAndre
Copy link
Collaborator Author

@mfarrugi How do you feel about this rule just being a Part of rust_library? Something that optionally gets done for libraries of crate type cdylib andstaticlib?

@mfarrugi
Copy link
Collaborator

mfarrugi commented Sep 13, 2020 via email

@UebelAndre
Copy link
Collaborator Author

Finally got around to working on this again. The thing I'm trying to figure out is how to handle transitive #[repr(C)] declarations. This would be easy if we had a rule that generated a macro that generated Cargo.toml files but I don't think we do. So for any target that wants to use the rust_cbindgen_library rule, I think I may have to write something that generates that in the same command as the cbindgen executable action so I can use the --crate argument. So this may sit a little bit longer but I do feel the current state is already much better than before.

@UebelAndre
Copy link
Collaborator Author

@mfarrugi Do you have any suggestions on how to run cbindgen against a crate defined in Bazel? cbindgen seems to act either on a crate root file or a Cargo.toml file (for extra context).

@@ -0,0 +1,7 @@
#if os(Linux)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also, I may delete the swift examples. They're not currently hooked up because I don't know how to restrict them to mac/linux and I want to make sure bazel build //... and bazel test //... works on all platforms for the example. It's upsetting that that currently does not work . Don't want to grow the issue. If anyone has any suggestions on how to achieve this, I'd love to hear them

@mfarrugi
Copy link
Collaborator

mfarrugi commented Oct 19, 2020 via email

@UebelAndre
Copy link
Collaborator Author

@mfarrugi would you mind explaining aspects to me? I don't think I ever quite understood them

@mfarrugi
Copy link
Collaborator

mfarrugi commented Oct 19, 2020 via email

@mfarrugi
Copy link
Collaborator

mfarrugi commented Nov 1, 2020 via email

@UebelAndre
Copy link
Collaborator Author

lol that doesn't really help. There needs to be a way to generate cargo metadata in order to use cbindgen transitively. This seems to have a wider scope than just an implementation detail for a cbindgen rule. I opened #458 after thinking about this a bit. Would the rules generate Cargo.toml files?

@UebelAndre
Copy link
Collaborator Author

Just pinging my last question 😅

@mfarrugi
Copy link
Collaborator

mfarrugi commented Nov 5, 2020

You can either generate the Cargo.toml from the regular rules or in an aspect for cbindgen, the implementation should be mostly the same for either approach.

@UebelAndre
Copy link
Collaborator Author

Currently running into a very frustrating issue:

ERROR: /Users/user/Code/rules_rust/examples/cbindgen/examples_cc/BUILD.bazel:116:22: Generating C++ C/C++ bindings for 'cbindgen/examples_cc/transitive_repr_c_cbindgen.h'.. failed (Exit 1): sandbox-exec failed: error executing command
  (cd /private/var/tmp/_bazel_user/1426ad6c00f8f7b996ddc75bef70871c/sandbox/darwin-sandbox/2072/execroot/examples && \
  exec env - \
    PATH='/Users/user/Library/Caches/bazelisk/downloads/bazelbuild/bazel-3.7.0-darwin-x86_64/bin:/Users/user/.cargo/bin:/Users/user/.cargo/bin:/Library/Frameworks/Python.framework/Versions/3.8/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS' \
    TMPDIR=/var/folders/hf/phjl9q7501gb5wt_qr4ltn3m0000gn/T/ \
  /usr/bin/sandbox-exec -f /private/var/tmp/_bazel_user/1426ad6c00f8f7b996ddc75bef70871c/sandbox/darwin-sandbox/2072/sandbox.sb /var/tmp/_bazel_user/install/3ac6e4bc67346f686d73708a27e81d33/process-wrapper '--timeout=0' '--kill_delay=15' bazel-out/host/bin/external/rules_rust_cbindgen__cbindgen__0_15_0/cargo_bin_cbindgen --config bazel-out/darwin-fastbuild/bin/cbindgen/examples_cc/transitive_repr_c_cbindgen.cbindgen.toml --output bazel-out/darwin-fastbuild/bin/cbindgen/examples_cc/transitive_repr_c_cbindgen.h bazel-out/darwin-fastbuild/bin/cbindgen/examples_cc/transitive_repr_c_lib.cargo) sandbox-exec failed: error executing command
  (cd /private/var/tmp/_bazel_user/1426ad6c00f8f7b996ddc75bef70871c/sandbox/darwin-sandbox/2072/execroot/examples && \
  exec env - \
    PATH='/Users/user/Library/Caches/bazelisk/downloads/bazelbuild/bazel-3.7.0-darwin-x86_64/bin:/Users/user/.cargo/bin:/Users/user/.cargo/bin:/Library/Frameworks/Python.framework/Versions/3.8/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS' \
    TMPDIR=/var/folders/hf/phjl9q7501gb5wt_qr4ltn3m0000gn/T/ \
  /usr/bin/sandbox-exec -f /private/var/tmp/_bazel_user/1426ad6c00f8f7b996ddc75bef70871c/sandbox/darwin-sandbox/2072/sandbox.sb /var/tmp/_bazel_user/install/3ac6e4bc67346f686d73708a27e81d33/process-wrapper '--timeout=0' '--kill_delay=15' bazel-out/host/bin/external/rules_rust_cbindgen__cbindgen__0_15_0/cargo_bin_cbindgen --config bazel-out/darwin-fastbuild/bin/cbindgen/examples_cc/transitive_repr_c_cbindgen.cbindgen.toml --output bazel-out/darwin-fastbuild/bin/cbindgen/examples_cc/transitive_repr_c_cbindgen.h bazel-out/darwin-fastbuild/bin/cbindgen/examples_cc/transitive_repr_c_lib.cargo)
ERROR: Parsing crate `libc`: cannot open file `/private/var/tmp/_bazel_user/1426ad6c00f8f7b996ddc75bef70871c/sandbox/darwin-sandbox/2072/execroot/examples/bazel-out/darwin-fastbuild/bin/external/cbindgen_cc_examples__libc__0_2_79/libc.cargo/../../../../../../external/cbindgen_cc_examples__libc__0_2_79/src/lib.rs`.
ERROR: Couldn't generate bindings for bazel-out/darwin-fastbuild/bin/cbindgen/examples_cc/transitive_repr_c_lib.cargo.

Given the current implementation, I don't understand why I'd be receiving this error.

@mfarrugi if you have a free moment, I could definitely use a second pair of eyes.

@mfarrugi
Copy link
Collaborator

mfarrugi commented Nov 9, 2020 via email

@UebelAndre
Copy link
Collaborator Author

Yeah, I'm not sure why the source from the external crates are not available for this action. If I iterate over and print the inputs depset I can see the source files represented as File objects but they're still not made available 😞

@UebelAndre
Copy link
Collaborator Author

Ok, I think I got something that mostly works but in order to parse transitive dependencies a cargo executable is required. I'm thinking of using cargo-raze to generate a way to build it and to add that to the rust_cbindgen_toolchain toolchain. What're your thoughts @mfarrugi ?

@UebelAndre
Copy link
Collaborator Author

UebelAndre commented Dec 1, 2020

I think generating Cargo.toml files is not the correct thing to do here. I think we need to generate a full cargo metadata json dump. This should avoid the need for a Cargo binary but I feel like this is a foggy area for me. I don't want to turn the rust rules into a wrapper for Cargo but Cargo is a massive part of the rust eco system that it might be worth providing a toolchain that provides a sandboxed version.

Would also love thoughts from @damienmg on this

@UebelAndre
Copy link
Collaborator Author

Ok, so the thing I'm thinking of doing at this point after #520 is to make a dedicated toolchain for cargo based on what's already a part of rust_toolchain and render a way to build it with cargo raze. Then update the new aspect to not just create Cargo.toml files to render a json dump of cargo metadata.

@UebelAndre
Copy link
Collaborator Author

I write an example of this using swift but will not be including it since the rules don't play nicely with Windows. So I'll put it here in case anyone was interested.

examples/cbindgen/examples_swift/BUILD.bazel

"""Example of `rust_cbindgen_library` rule with a Swift test binary"""

load("@io_bazel_rules_rust//rust:rust.bzl", "rust_library")
load("@io_bazel_rules_rust//cbindgen:cbindgen.bzl", "rust_cbindgen_library")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_test")

rust_library(
    name = "cbindgen_example_lib",
    srcs = ["@examples//cbindgen:rust_srcs"],
    crate_type = "staticlib",
)

rust_cbindgen_library(
    name = "cbindgen_example",
    lang = "c",
    lib = ":cbindgen_example_lib",
    tags = [
        "swift_module=CBindgenExample",
    ],
)

swift_test(
    name = "cbindgen_example_test",
    srcs = [
        "CBindgenExample.swift",
        "main.swift",
    ],
    deps = [
        ":cbindgen_example",
    ],
)

examples/cbindgen/examples_swift/main.swift

#if os(Linux)
import XCTest

XCTMain([
  testCase(CBindgenExample.allTests),
])
#endif

examples/cbindgen/examples_swift/CBindgenExample.swift

import XCTest

import CBindgenExample


class CBindgenSwiftExample: XCTestCase {

    func testInt() {
        XCTAssertEqual(CBindgenExample.int_from_rust(), 1337);
        CBindgenExample.int_into_rust(9999);
    }

    func testString() {
        var buffer: UnsafeMutablePointer<Int8>?;
        let size = CBindgenExample.string_from_rust(&buffer);
        
        XCTAssert(size > 0);
        XCTAssertEqual(String(cString: buffer!), "Hello Fellow Rustacean!");
        CBindgenExample.free_rust_string(buffer);

        CBindgenExample.string_into_rust("Hello World!")
    }

    func testStruct() {
        let point = CBindgenExample.struct_from_rust();
        XCTAssert((point.x - 3.14) < 0.01);
        XCTAssert((point.y - 42.0) < 0.01);

        var point_swift = CBindgenExample.Point2D();
        point_swift.x = 42.0;
        point_swift.y = 3.14;
        CBindgenExample.struct_into_rust(point_swift);
    }

    static var allTests = [
        ("testInt", testInt),
        ("testString", testString),
        ("testStruct", testStruct),
    ]
}

@UebelAndre
Copy link
Collaborator Author

UebelAndre commented Dec 5, 2020

running into one last error on windows:

Executing tests from @examples//cbindgen/examples_py:cbindgen_example_test
-----------------------------------------------------------------------------
E
======================================================================
ERROR: setUpClass (__main__.TestCBindgenExample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "\\?\C:\temp\Bazel.runfiles_ibwlc4i2\runfiles\examples\cbindgen\examples_py\cbindgen_example.py", line 45, in setUpClass
    assert SHARED_LIB_PATH.exists()
AssertionError

----------------------------------------------------------------------
Ran 0 tests in 0.000s

FAILED (errors=1)
!!!!!!!!!!! external\examples\cbindgen\examples_py\cbindgen_example_lib--1699860201.dll

But I don't have windows hardware to debug this 😢

"""
return target.label.workspace_root.startswith("external")

def _clone_external_crate_sources(ctx, target):
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This should ideally be unnecessary but when resolving the generaged Cargo manifest for external crates, the sources could not be found. This copies them so cbindgen (and ideally other rules) can locate those files.

# file does not work off of a crates extern/use crates
# declared in source. To include transitive symbols, a
# custom config must be created until this feature is
# implemented: https://github.com/eqrion/cbindgen/issues/7
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried finding some smarter ways to transitively gather symbols based on either how the dependencies were defined and by parsing some rust sources but ultimately mozilla/cbindgen#7 is the correct solution here. If users need symbols defined in other crates, they'll have to use something like the following

[parse]
parse_deps = true
clean = true
extra_bindings = [
# deps go here
]

@UebelAndre
Copy link
Collaborator Author

@mfarrugi Ok! This is ready for review now! 🎉 😄

@UebelAndre
Copy link
Collaborator Author

@smklein @illicitonion @damienmg Also, would any of you be able to review this as well?

@UebelAndre
Copy link
Collaborator Author

Oh, and just to share, there seems to be a pretty neat alternative at https://github.com/dtolnay/cxx/tree/master/tools/bazel

Interested in hearing some thoughts on that vs the bindgen/(potential)cbindgen rules here.

@UebelAndre
Copy link
Collaborator Author

Gonna close this. Doesn't look like there's any interest in this rule and I don't have the bandwidth to continue to try to push this forward. I've attached a dump of this branch below in case I decide to delete the branch from my fork. The rules here work and have tests based on the feedback in this PR. I hope someone finds this useful 😄

rules_rust-cbindgen.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants