Reindeer

repository·main·Indexed 42 days ago

https://github.com/facebookincubator/reindeer

A toolset for importing Rust crates from sources like crates.io or git and automatically generating Buck build rules for them. It enables managing third-party Rust dependencies within monorepos by translating Cargo.toml manifests into Buck targets, allowing Buck to invoke rustc directly without requiring Cargo at build time.

Tokens
14.4K
Snippets
49
Records
73
Agent score
54%

What's inside Reindeer

  1. Getting started with Reindeer

    main

    Reindeer is a toolset for importing Rust crates (from crates.io, git repos, etc.) and generating Buck build rules for them. It is primarily designed to manage third-party dependencies in monorepos using the Buck build system.

    To learn how to use Reindeer, you can explore the small, complete examples provided in the examples directory of the repository. For more comprehensive documentation, refer to docs/MANUAL.md.

  2. What is Reindeer and how does it work?

    main

    Reindeer is a tool that translates a transitive dependency graph of Cargo.toml manifests into Buck targets. This allows the Buck build system to leverage Cargo libraries and binaries as dependencies.

    Key Concepts

    • Ahead-of-Time Translation: The translation from Cargo to Buck happens when you add or update dependencies. The output is intended to be checked into source control.
    • No Cargo at Build Time: At build time, Buck directly invokes rustc to compile crates; Cargo is not involved in the build process.
    • Vendoring: Reindeer supports an optional vendor mode.
      • With reindeer vendor: Source code is downloaded from crates.io and written into your repository. This is strongly recommended for monorepo-style workflows as it ensures no network access is required during build time.
      • Without reindeer vendor: The generated Buck targets include steps to perform the crate download during the build process.
  3. Use the Fixups system for build scripts and C dependencies

    main

    The fixups system allows you to handle complex third-party crate requirements, such as running build.rs scripts or managing C dependencies.

    Running Build Scripts (build.rs)

    To instruct Reindeer to compile and run a crate's build script and transform its stdout into rustc flags, use the buildscript.run option in a fixup configuration.

    buildscript.run = true

    Providing Cargo Environment Variables

    If a build script relies on Cargo environment variables (like env!("CARGO_PKG_NAME")), you must explicitly enable them, as Buck does not provide these by default:

    cargo_env = true
    buildscript.run = true

    Handling C Dependencies (cxx_library)

    For crates that use the cc crate to invoke a C compiler (e.g., blake3, zstd), you can manually translate cc invocations into [[cxx_library]] fixups. Reindeer converts these into cxx_library targets for Buck. For complex crates like zstd or winapi, check the Buck2 repository's fixups folder for existing implementations.

    # Example: Enabling build script and cargo env
    cargo_env = true
    buildscript.run = true
  4. Handle cross-compilation and no-std with execution-platform

    main

    By default, Reindeer assumes all platforms are for the same execution environment. For cross-compilation or no-std targets, you must explicitly define whether a platform is an execution platform using execution-platform = true or false.

    This prevents features enabled by build scripts or proc-macros on the host (execution) platform from being incorrectly applied to the target (non-execution) platform. Features/dependencies enabled on a platform where execution-platform = false will instead be applied to all platforms where execution-platform = true.

    [platform.linux-x86_64]
    target = "x86_64-unknown-linux-gnu"
    execution-platform = true
    
    [platform.riscv64gc]
    target = "riscv64gc-unknown-none-elf"
    execution-platform = false
  5. How to use Cargo and Buck together in a hybrid project

    main

    Reindeer allows you to maintain a project that uses both Cargo and Buck simultaneously. This setup is ideal for developers who want to use Cargo-specific tooling (like cargo fmt or rust-analyzer) for local development and contribution, while using Buck for the final build process.

    The core mechanism is a reindeer.toml file in the project root, which orchestrates the relationship between a Cargo workspace and a Buck package (typically named third-party) where Cargo dependencies are "buckified."

  6. Extend Buck rules with custom Starlark macros

    main

    Reindeer can be customized to wrap generated rules with your own Starlark code. This is useful for applying global logic, such as customizing opt-levels for third-party code.

    Workflow for custom macros:

    1. Define your Starlark macros in a .bzl file (e.g., third-party/macros/rust_third_party.bzl).
    2. Import these macros into the generated third-party/BUCK file.
    3. Configure reindeer.toml to instruct Reindeer to generate macro calls for each crate instead of using the default rules.
  7. Apply platform-specific fixups

    main

    By default, fixups apply to all platforms. To target specific platforms (e.g., only Linux x86_64), use a TOML section starting with cfg(. The section will only be applied if the cfg predicate evaluates to true based on the Reindeer platform's target information.

    Example of a top-level platform-specific fixup:

    ['cfg(all(target_os = "linux", target_arch = "x86_64"))']
    cfgs = ["use_libunwind"]
    extra_deps = ["//third-party/libunwind:unwind-ptrace"]

    Example of a platform-specific C++ library:

    buildscript.run = false
    
    [['cfg(target_os = "linux")'.cxx_library]]
    name = "sysinfo-linux"
    srcs = ["c/linux.c"]
    headers = ["c/*.h"]
  8. Use external crates to avoid duplicating sources

    main

    If your project shares many dependencies with a larger third-party tree, you can use the [extern_crates] configuration to avoid duplicating crate sources. Instead of building common crates (like serde or libc) from local vendored source, Reindeer generates alias targets that point to existing external Buck targets.

    Configuration

    Add an [extern_crates] section to your reindeer.toml:

    [extern_crates]
    target = "//third-party/rust:{name}"
    vendored = [
        "my-custom-crate",
        "another-local-crate",
    ]
    • target: A Buck target pattern where {name} is replaced by the crate name. Non-workspace crates not in the vendored list will receive an alias to this target.
    • vendored: A list of crate names that should be built from local vendored source. These receive full rust_library targets.

    How it works

    1. Workspace members: Always built from source automatically.
    2. Vendored crates: Listed in reindeer.toml, built from the local vendor/ directory with rust_library targets.
    3. All other crates: Receive a versioned alias target (e.g., serde-1.0.228) pointing to the external target (e.g., //third-party/rust:serde). Their dependencies are not recursively processed by Reindeer, as the external target manages its own graph.
  9. Apply version-specific fixups

    main

    If multiple versions of the same Rust crate exist in your dependency graph, you can target a specific subset of versions using the version predicate within a cfg( section. This is useful when a fixup is only required for newer versions of a crate.

    Example:

    ['cfg(version = ">=0.18")']
    extra_srcs = ["src/tmux_cc/tmux.pest"]
  10. Configure Reindeer dependencies

    main

    Reindeer manages third-party Rust dependencies within the third-party/ directory. You control which packages are imported by editing the Cargo.toml file located in that directory. You can use standard Cargo syntax to specify packages, features, patches, and other settings.

    Key files in third-party/:

    • reindeer.toml: Reindeer's primary configuration file. Paths in this file are relative to the directory containing it.
    • Cargo.toml: The file you edit to manage dependencies.
    • Cargo.lock: The file containing resolved dependencies.
    • BUCK: The generated Buck build rules (created after running setup).
    • .gitignore: Used by Reindeer to exclude specific files from vendored checksums to prevent Cargo errors.
  11. Add and buckify new Cargo dependencies

    main

    When you add a new dependency to your Cargo project, you must "buckify" it so that Buck can recognize and use it. Follow these steps:

    1. Add the dependency using Cargo: cargo add -p <package_name> <dependency_name>
    2. Run the reindeer buckify command to sync the dependencies into the Buck package: reindeer buckify
    3. Update your BUCK file (e.g., project/BUCK) to include the new dependency from the third-party package (e.g., //third-party:<dependency_name>) in your deps list.
    # 1. Add dependency via Cargo
    cargo add -p project rand
    
    # 2. Sync dependencies to Buck
    reindeer buckify
    
    # 3. Manually update project/BUCK to include //third-party:rand in deps