Skip to content

_add_native_link_flags generates both static and dynamic linking flags preventing hermetic cross-compilation #3504

@punya

Description

@punya

Summary

The _add_native_link_flags function in rust/private/rustc.bzl automatically generates both static (-lstatic=) and dynamic (-l) linking flags for cc_library dependencies. This prevents hermetic cross-compilation scenarios where only static libraries are available.

Problem Description

When cross-compiling a Rust binary that depends on cc_library targets, _add_native_link_flags generates linking flags like:

-lstatic=mylib -lmylib

In hermetic cross-compilation environments (e.g., macOS host targeting Linux), only static libraries (.a) are available, but the dynamic flags (-lmylib) cause linker failures:

error: linking with `zig` failed: exit status: 1
= note: /usr/bin/ld: cannot find -lmylib: No such file or directory

Minimal Reproduction

A complete, self-contained reproduction is available at:
https://gist.github.com/punya/a013c30ea31ca4ef0386968e7f21849d

The reproduction demonstrates:

  1. A local cc_library that builds successfully (creating both .a and .so files)
  2. A rust_binary that fails during hermetic cross-compilation
  3. Evidence that even linkstatic=True doesn't solve the problem
  4. A working manual workaround using rustc_flags
  5. Why the workaround is insufficient for real-world use

Run ./test.sh to see all scenarios in action.

Root Cause

The platform-specific link flag generators (e.g., _make_link_flags_default for Linux) in _add_native_link_flags are hardcoded to generate both static and dynamic flags for each cc_library dependency. There's no mechanism to generate only static flags.

Proposed Solutions (Ranked by Preference)

Option 1 (Preferred): Automatic Detection

Automatically detect when only static libraries are available and generate only static flags. The function could:

  • Check if dynamic libraries (.so, .dylib, .dll) exist alongside static libraries
  • In hermetic cross-compilation, only static libraries are typically available
  • Fall back to static-only linking when dynamic libraries are missing

Option 2: Add static_only_linking attribute

Add a new attribute to rust_binary and rust_library rules:

rust_binary(
    name = "my_binary",
    static_only_linking = True,  # Generate only -lstatic= flags
    deps = [":mylib"],
)

Option 3: Use linking modifiers

Leverage Rust's native linking modifiers (RFC 2951) when available:

-l static:+whole-archive=mylib

Impact

This affects any project using:

  • Hermetic cross-compilation with cc_library dependencies
  • Static-only build environments
  • Embedded targets where dynamic linking isn't available
  • Container builds targeting minimal base images
  • Projects currently forced to use cumbersome manual workarounds that don't scale

Current Workarounds

A manual workaround exists but is insufficient:

Manual rustc_flags workaround:

rust_binary(
    name = "my_binary",
    deps = [":mylib"],
    rustc_flags = [
        "-C", "link-arg=-Wl,-Bstatic",
        "-C", "link-arg=-lmylib",
        "-C", "link-arg=-Wl,-Bdynamic",
    ],
)

Limitations of this workaround:

  • Requires manually specifying every library name
  • Becomes unwieldy for complex dependency trees
  • Defeats the purpose of Bazel's automatic dependency management
  • Must be repeated for every rust_binary target

Other insufficient workarounds:

  • Avoid cc_library dependencies in Rust targets
  • Use cc_binary as the final linker instead of rust_binary
  • Patch rules_rust locally
  • Abandon hermetic cross-compilation

Additional Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions