Skip to content

Make Bazel Work in MacOS for Iceoryx2

In iceoryx2, it uses bazel to build C/C++ bindings. Hence, they want to use bazel for rust for a better unification in iceoryx2.

Currently foreign_cc is used to build the C binding and cc_library to build the C++ bindings. In order to fully exhaust the capabilities of bazel, the C binding and therefore the Rust code should be build with rules_rust.

When I tried it, I found it doesn't work in my MacOS. This blog records some knowledge when I learned bazel and how I solved the problem. It doesn't talk about why we use bazel in a rust project.

Blockers when I learned Bazel

Bazel looks like a light-weight nix, when I first saw it. But well, I didn't master nix as well:( So it's syntax took me a lot of time to learn. The gpt-4o and claude-3.5 did quite bad for bazel questions. It's worthy reading the bazel concept guides.

The label notation such as @blabla//a/b/c, //a/b/c and //a/b/c:d/f is quite confusing at beginning.

The single-@ notates an apparent repo, which depends on the context. You can use this to access the repos you defined(usually they're defined by http_archive or git_repository).

Problems and how I solve them

Download different binary for different OS

I was amazing that bazel doesn't support automatically selection based on OS. However, it will strictly follow the configuration and then macos cannot recognize the downloaded binary for linux.

maybe(
    name = "bindgen",
    repo_rule = http_archive,
    sha256 = "b7e2321ee8c617f14ccc5b9f39b3a804db173ee217e924ad93ed16af6bc62b1d",
    strip_prefix = "bindgen-cli-x86_64-unknown-linux-gnu",
    urls = ["https://github.com/rust-lang/rust-bindgen/releases/download/v0.69.5/bindgen-cli-x86_64-unknown-linux-gnu.tar.xz"],
    build_file_content = """
filegroup(
    name = "bindgen-cli",
    srcs = ["bindgen"],
    visibility = ["//visibility:public"],
)
    """,
)   

To fix this, we need to allow bazel download different bindgen based on OS so another target is required.

maybe(
    name = "bindgen-macos",
    repo_rule = http_archive,
    strip_prefix = "bindgen-cli-aarch64-apple-darwin",
    urls = ["https://github.com/rust-lang/rust-bindgen/releases/download/v0.69.5/bindgen-cli-aarch64-apple-darwin.tar.xz"],
    build_file_content = """
filegroup(
    name = "bindgen-cli",
    srcs = ["bindgen"],
    visibility = ["//visibility:public"],
)
    """,
)

Now the target bindgen-cli could be accessed by @bindgen-macos//:bindgen-cli and @bindgen//:bindgen-cli. When we use the bindgen, we need to define an alias to select so the user won't care about the underlying downloading tool.

alias(
    name = "bindgen-cli",
    actual = select({
        "@bazel_tools//src/conditions:darwin": "@bindgen-macos//:bindgen-cli",
        "//conditions:default": "@bindgen//:bindgen-cli",
    }),
)

genrule(
    name = "iceoryx2-pal-posix-bindgen",
    srcs = [
        "src/c/posix.h",
    ],
    outs = ["posix_generated.rs"],
    cmd = "$(execpath :bindgen-cli) --use-core --blocklist-type max_align_t $(location src/c/posix.h) --output $(OUTS)",
    tools = [":bindgen-cli"],
)

Build cbindgen from source

Unlike bindgen, cbindgen only provides a binary release for ubuntu. To support cbindgen on macOS, we need to build the cbindgen from source. You can view rust_binary example for more details.

1. download source code by http_archive

rust_repositories()

http_archive(
    name = "cbindgen_source",
    urls = ["https://github.com/mozilla/cbindgen/archive/refs/tags/0.26.0.tar.gz"],
    strip_prefix = "cbindgen-0.26.0",  
    build_file_content = """
filegroup(
    name = "srcs",
    srcs = glob(["src/**/*.rs", "build.rs", "Cargo.toml",  "Cargo.lock"]),
    visibility = ["//visibility:public"],

)
exports_files(["Cargo.toml", "Cargo.lock"], visibility = ["//visibility:public"])
"""
)

2. define and download deps by crates_repository

crates_repository is a rule for defining and downloading Rust dependencies (crates). There is no automatically downloading when building in bazel, so we need to define the deps manually.

load("@rules_rust//rust:defs.bzl", "rust_library")
load("@rules_rust//crate_universe:defs.bzl", "crates_repository")

crates_repository(
    name = "cbindgen_deps",
    cargo_lockfile = "@cbindgen_source//:Cargo.lock",
    manifests = ["@cbindgen_source//:Cargo.toml"],
)

load("@cbindgen_deps//:defs.bzl", cbindgen_deps = "crate_repositories")

cbindgen_deps()

3. build binary by rust_binary

After downloading the source and its deps, we can use rust_binary to build. Here we need to fill the deps manually by checking the project's Cargo.toml.

load("@rules_rust//rust:defs.bzl", "rust_binary")

# load("@cbindgen_source//:BUILD.bazel", "srcs")
rust_binary(
    name = "cbindgen-cli",
    srcs = [
        "@cbindgen_source//:srcs",
    ],
    data = [
        "@cbindgen_source//:Cargo.toml",
        "@cbindgen_source//:Cargo.lock",
    ],
    edition = "2018",
    deps = [
        "@cbindgen_deps//:clap",
        "@cbindgen_deps//:indexmap",
        "@cbindgen_deps//:log",
        "@cbindgen_deps//:serde",
        "@cbindgen_deps//:serde_json",
        "@cbindgen_deps//:tempfile",
        "@cbindgen_deps//:toml",
        "@cbindgen_deps//:proc-macro2",
        "@cbindgen_deps//:quote",
        "@cbindgen_deps//:heck",
        "@cbindgen_deps//:syn",
    ],
)