`rust_test(crate = ...)` does not stage the crate's transitive dynamic libraries into runfiles

Đang mở Phù hợp với người mới
#4,309 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 2 ngày

@Wito-1 đang làm issue này rồi.

Từ ngày 30/9/2026.

  • #4310 của @Wito-1 — đang mở

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
88/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
rust
Lĩnh vực
build-system

Hướng nghiên cứu

Bắt đầu trong rust/private/rustc.bzl tại rustc_compile_action và so sánh vòng lặp runfiles của dynamic library với _compute_rpaths và dep_info.transitive_noncrates. Chạy bản tái hiện Bazel được cung cấp bằng bazel test //repro:all --cache_test_results=no; hoàn tất khi bài kiểm thử dựa trên crate stage libfoo.so và không thay đổi hành vi của bài kiểm thử dựa trên deps.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Summary

rustc_compile_action stages a binary's CcInfo dynamic libraries into its runfiles by walking only the rule's own deps attribute, while _compute_rpaths walks the full transitive linker-input set. A rust_test that reaches a dynamic library only through its crate attribute therefore links against it, has DT_NEEDED for it and a RUNPATH into the _solib_<cpu> directory where Bazel would stage it, but the file is never put in the runfiles. The test then fails at loader time on any host that has no copy of the library on the system search path (a sandbox, a remote executor, CI).

The same crate exposed through a rust_library listed in deps works, because establish_cc_info merges the transitive CcInfo into the library's own provider, so the deps-only walk happens to see it.

Reproduction

# BUILD.bazel
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_import")
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")

cc_binary(
    name = "libfoo.so",
    srcs = ["foo.c"],
    linkshared = True,
)

cc_import(
    name = "foo",
    shared_library = ":libfoo.so",
)

rust_library(
    name = "uses_foo",
    srcs = ["lib.rs"],
    deps = [":foo"],
)

# Reaches libfoo.so only through `crate`.
rust_test(
    name = "uses_foo_crate_test",
    crate = ":uses_foo",
)

# Reaches libfoo.so through a rust_library in `deps`.
rust_test(
    name = "uses_foo_deps_test",
    srcs = ["test.rs"],
    deps = [":uses_foo"],
)
// foo.c
int foo_answer(void) { return 42; }
// lib.rs
unsafe extern "C" {
    fn foo_answer() -> i32;
}

pub fn answer() -> i32 {
    unsafe { foo_answer() }
}

#[cfg(test)]
mod tests {
    #[test]
    fn answers() {
        assert_eq!(super::answer(), 42);
    }
}
// test.rs
#[test]
fn answers() {
    assert_eq!(uses_foo::answer(), 42);
}
$ bazel test //repro:all --cache_test_results=no
//repro:uses_foo_deps_test     PASSED in 0.0s
//repro:uses_foo_crate_test    FAILED in 0.0s

$ cat bazel-testlogs/repro/uses_foo_crate_test/test.log
.../uses_foo_crate_test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

Observed

Both binaries link identically. readelf -d on each shows the same entries:

(RUNPATH)  Library runpath: [$ORIGIN/../../../_solib_k8/_U_S_Srepro_Cfoo___Urepro]
(NEEDED)   Shared library: [libfoo.so]

Only the runfiles differ. From bazel cquery --output=starlark over providers(target)["DefaultInfo"].default_runfiles.files:

//repro:uses_foo_crate_test: uses_foo_crate_test
//repro:uses_foo_deps_test:  libfoo.so, uses_foo_deps_test

Expected

A binary's runfiles contain every dynamic library its link consumed, i.e. the same set the rpath computation already walks. Whether a dylib is reached via deps or via crate should not change what is shipped.

Cause

rust/private/rustc.bzl, in rustc_compile_action (0.70.0, lines 1753–1762; main has the same loop plus link_deps):

    if crate_info.type in ["bin", "cdylib", "staticlib"]:
        dynamic_libraries = ctx.runfiles(files = [
            library_to_link.dynamic_library
            for dep in getattr(ctx.attr, "deps", [])
            if CcInfo in dep
            for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list()
            for library_to_link in linker_input.libraries
            if _is_dylib(library_to_link) and library_to_link.dynamic_library
        ])
        transitive_runfiles.append(dynamic_libraries)

ctx.attr.crate is not in ctx.attr.deps, and the crate's own DefaultInfo.default_runfiles (merged a few lines above) carry no dylibs because an rlib is not in the ["bin", "cdylib", "staticlib"] set. _compute_rpaths, by contrast, iterates dep_info.transitive_noncrates, which collect_deps builds from crate_info.deps and therefore does include the crate's dependencies.

Proposed fix

Iterate the same transitive set the rpath computation uses. dep_info is already in scope. This cannot add anything the binary does not need: _is_dylib keeps only libraries that were linked dynamically, which are exactly the ones the binary has as DT_NEEDED.

--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -1751,11 +1751,15 @@
         for target in runfiles_attr:
             transitive_runfiles.append(target[DefaultInfo].default_runfiles)
     if crate_info.type in ["bin", "cdylib", "staticlib"]:
+        # Stage every dynamic library the link consumed, walking the same
+        # transitive set `_compute_rpaths` does. Walking only `ctx.attr.deps`
+        # misses a dylib reached solely through a `rust_test(crate = ...)`'s
+        # crate: the binary NEEDs it and its rpath points at the solib dir, but
+        # the file is never shipped, so it fails at loader time wherever the
+        # host has no copy.
         dynamic_libraries = ctx.runfiles(files = [
             library_to_link.dynamic_library
-            for dep in getattr(ctx.attr, "deps", [])
-            if CcInfo in dep
-            for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list()
+            for linker_input in dep_info.transitive_noncrates.to_list()
             for library_to_link in linker_input.libraries
             if _is_dylib(library_to_link) and library_to_link.dynamic_library
         ])

With this applied, the crate = test above stages libfoo.so and passes. In a larger repository, diffing default_runfiles for 16 rust_test/rust_binary targets before and after the change altered only the one target that used crate =; every other target's runfiles were byte-identical, as expected from the establish_cc_info merge.

Environment

  • rules_rust 0.70.0 (bzlmod); the loop is unchanged on main as of 0.74.0
  • Bazel 9.2.0, Linux x86_64
  • Reproduces locally under sandboxing and on remote execution; passes on a host that happens to have the library on the default loader path, which is why it is easy to miss
Ngôn ngữ chính
Starlark
Star
854
Fork
684
Merge trung bình
2 ngày 22 giờ
Pull request đã merge (30 ngày)
21

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của bazelbuild/rules_rust

Tất cả issue của bazelbuild/rules_rust

Issue tương tự

Thêm issue về Build System

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.