Repository: rust
Issue: Rust binaries cannot load relocatable libraries on macOS without `-rpath`
Labels: A-linkage,O-macos,C-bug

<!--
Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,
along with any information you feel relevant to replicating the bug.
-->

Hi all,

I tried to link against a relocatable shared library on macOS with the following demo project: https://github.com/amyspark/rust-demo-rpath

The library `rpath-meson` is built with Ninja:

```
cd rpath-meson
ninja
```

The library at this point is relocatable, as described in ["Dynamic Library Identification"](https://forums.developer.apple.com/forums/thread/736719) over at the Apple Developer forums:

<details><summary>otool output</summary>

```
❯ otool -L ./rpath-meson/librpath_meson.dylib 
./rpath-meson/librpath_meson.dylib:
        @rpath/librpath_meson.dylib (compatibility version 0.0.0, current version 0.0.0)

❯ otool -l ./rpath-meson/librpath_meson.dylib
./rpath-meson/librpath_meson.dylib:
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 312
  segname __TEXT
   vmaddr 0x0000000000000000
   vmsize 0x0000000000004000
  fileoff 0
 filesize 16384
  maxprot 0x00000005
 initprot 0x00000005
   nsects 3
    flags 0x0
Section
  sectname __text
   segname __TEXT
      addr 0x0000000000003f90
      size 0x000000000000000d
    offset 16272
     align 2^4 (16)
    reloff 0
    nreloc 0
     flags 0x80000400
 reserved1 0
 reserved2 0
Section
  sectname __cstring
   segname __TEXT
      addr 0x0000000000003f9d
      size 0x000000000000000c
    offset 16285
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x00000002
 reserved1 0
 reserved2 0
Section
  sectname __unwind_info
   segname __TEXT
      addr 0x0000000000003fac
      size 0x0000000000000048
    offset 16300
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x00000000
 reserved1 0
 reserved2 0
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __LINKEDIT
   vmaddr 0x0000000000004000
   vmsize 0x0000000000004000
  fileoff 16384
 filesize 424
  maxprot 0x00000001
 initprot 0x00000001
   nsects 0
    flags 0x0
Load command 2
          cmd LC_ID_DYLIB
      cmdsize 56
         name @rpath/librpath_meson.dylib (offset 24)
   time stamp 1 Thu Jan  1 00:00:01 1970
      current version 0.0.0
compatibility version 0.0.0
Load command 3
      cmd LC_DYLD_CHAINED_FIXUPS
  cmdsize 16
  dataoff 16384
 datasize 48
Load command 4
      cmd LC_DYLD_EXPORTS_TRIE
  cmdsize 16
  dataoff 16432
 datasize 16
Load command 5
     cmd LC_SYMTAB
 cmdsize 24
  symoff 16456
   nsyms 10
  stroff 16616
 strsize 192
Load command 6
            cmd LC_DYSYMTAB
        cmdsize 80
      ilocalsym 0
      nlocalsym 9
     iextdefsym 9
     nextdefsym 1
      iundefsym 10
      nundefsym 0
         tocoff 0
           ntoc 0
      modtaboff 0
        nmodtab 0
   extrefsymoff 0
    nextrefsyms 0
 indirectsymoff 0
  nindirectsyms 0
      extreloff 0
        nextrel 0
      locreloff 0
        nlocrel 0
Load command 7
     cmd LC_UUID
 cmdsize 24
    uuid 42B5791C-F7EB-36DF-9F5B-123FBCC7C095
Load command 8
      cmd LC_BUILD_VERSION
  cmdsize 32
 platform 1
    minos 12.0
      sdk 13.1
   ntools 1
     tool 3
  version 820.1
Load command 9
      cmd LC_SOURCE_VERSION
  cmdsize 16
  version 0.0
Load command 10
      cmd LC_FUNCTION_STARTS
  cmdsize 16
  dataoff 16448
 datasize 8
Load command 11
      cmd LC_DATA_IN_CODE
  cmdsize 16
  dataoff 16456
 datasize 0

```
</details>

This library is made available to Cargo through `pkg-config` using:

```
❯ env PKG_CONFIG_PATH=$PWD/rpath-meson RUST_BACKTRACE=1 cargo run
```

I expected to see this happen:
```
❯ ./target/debug/rpath-demo
"Hello world"
```

Instead, this happened:
```
❯ ./target/debug/rpath-demo
dyld[5172]: Library not loaded: '@rpath/librpath_meson.dylib'
  Referenced from: '/Users/amalia/Documents/rust-demo-rpath/target/debug/rpath-demo'
  Reason: tried: '/Users/amalia/Documents/rust-demo-rpath/target/debug/deps/librpath_meson.dylib' (no such file), '/Users/amalia/Documents/rust-demo-rpath/target/debug/librpath_meson.dylib' (no such file), '/usr/local/Cellar/rust/1.79.0/lib/rustlib/x86_64-apple-darwin/lib/librpath_meson.dylib' (no such file), '/Users/amalia/lib/librpath_meson.dylib' (no such file), '/usr/local/lib/librpath_meson.dylib' (no such file), '/usr/lib/librpath_meson.dylib' (no such file)
fish: Job 1, 'env PKG_CONFIG_PATH=$PWD/rpath-…' terminated by signal SIGABRT (Abort)
```

This is because Cargo does not insert RPATH entries on its own. From searching older issues, a list of which is attached below, it seems that Cargo expects the environment to add the requisite flags. I have added such a flag through the pkg-config module; even though it is properly detected by `pkg-config` and forwarded as a `rustc-link-arg` entry, `rustc-link-arg` flags are skipped by Cargo. This seems to be a known issue:

- https://github.com/rust-lang/cargo/issues/9966#issuecomment-1736268619 (inability to set RPATH entries within Rust source code)
- https://github.com/rust-lang/cargo/issues/5077 (setting arbitrary `-rpath` flags is disallowed)
- https://github.com/rust-lang/cargo/issues/9554#issuecomment-888426982 (detecting and using `DEP_*` environment variables for that)
- https://github.com/rusterlium/rustler/pull/192 (linker flags inserted externally to fix producing a loadable module on macOS)

Using `DEP_*` environment variables is very vaguely documented in https://github.com/rust-lang/cargo/issues/7846#issuecomment-1792599655 (there's no mention if `cargo:rustc-link-arg`s are exposed like `cargo:include` entries). However, it does not scale because it needs the participation of all consumers of the relocatable library.

Using a pkg-config module or other such tool would still need Cargo to support bubbling `rustc-link-arg`s up to those crates generating loadable binaries.

Would it be possible for Rust/Cargo to consider bubbling up linker flags from `-sys` crates up to the apex consumer (shared library/executable), or insert them on its own?

### Meta
<!--
If you're using the stable version of the compiler, you should also check if the
bug also exists in the beta or nightly versions.
-->

`rustc --version --verbose`:
```
rustc 1.79.0 (129f3b996 2024-06-10) (Homebrew)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: x86_64-apple-darwin
release: 1.79.0
LLVM version: 18.1.7
```

<!--
Include a backtrace in the code block by setting `RUST_BACKTRACE=1` in your
environment. E.g. `RUST_BACKTRACE=1 cargo build`.
-->
<details><summary>Backtrace</summary>
<p>

```
rust-demo-rpath on  main [⇡] via 🦀 v1.79.0 
❯ env PKG_CONFIG_PATH=$PWD/rpath-meson RUST_BACKTRACE=1 cargo run
   Compiling rpath-sys v0.1.0 (/Users/amalia/Documents/rust-demo-rpath/rpath-sys)
   Compiling libc v0.2.155
   Compiling rpath-demo v0.1.0 (/Users/amalia/Documents/rust-demo-rpath/rpath-demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.25s
     Running `target/debug/rpath-demo`
dyld[5172]: Library not loaded: '@rpath/librpath_meson.dylib'
  Referenced from: '/Users/amalia/Documents/rust-demo-rpath/target/debug/rpath-demo'
  Reason: tried: '/Users/amalia/Documents/rust-demo-rpath/target/debug/deps/librpath_meson.dylib' (no such file), '/Users/amalia/Documents/rust-demo-rpath/target/debug/librpath_meson.dylib' (no such file), '/usr/local/Cellar/rust/1.79.0/lib/rustlib/x86_64-apple-darwin/lib/librpath_meson.dylib' (no such file), '/Users/amalia/lib/librpath_meson.dylib' (no such file), '/usr/local/lib/librpath_meson.dylib' (no such file), '/usr/lib/librpath_meson.dylib' (no such file)
fish: Job 1, 'env PKG_CONFIG_PATH=$PWD/rpath-…' terminated by signal SIGABRT (Abort)
```

</p>
</details>