Cargo using wrong target to build dependencies

I am having an issue oxidizing a C++ project that needs an
ancient 32-bit GCC version to build. The build machine is an
AMD64 multilib setup, the target is i686. The Rust component
builds fine as 64 bits and also as 32 bit using a more recent GCC
8.3 for linking. But as soon as I switch to the system compiler
-- which is i686 only -- cargo fails to pass the correct linker
flags down to dependencies which causes linking to fail.

For an MNWE, adding a dependency on memchr to a skeleton
project created using cargo init foo is sufficient to
reproduce the issue. This is an excerpt of what happens:

# cargo build  --target i686-unknown-linux-gnu
   Compiling memchr v2.3.0
     Running `rustc --crate-name build_script_build /datastore/dev/rust/cargo/registry/memchr-2.3.0/build.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C debuginfo=2 --cfg 'feature="default"' --cfg 'feature="std"' -C metadata=44fb1dc86bc067ac -C extra-filename=-44fb1dc86bc067ac --out-dir /mnt/host-src/i2n/tmp/test-link/target/debug/build/memchr-44fb1dc86bc067ac -L dependency=/mnt/host-src/i2n/tmp/test-link/target/debug/deps --cap-lints allow`
error: linking with `cc` failed: exit code: 1
  |
  = note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/datastore/dev/rust/lib/rustlib/x86_64-unknown-linux-gnu/lib" [[[ … snip till eol … ]]]
  = note: /usr/bin/ld: i386:x86-64 architecture of input file `/mnt/host-src/i2n/tmp/test-link/target/debug/build/memchr-44fb1dc86bc067ac/build_script_build-44fb1dc86bc067ac.build_script_build.4gqz6kdo-cgu.0.rcgu.o' is incompatible with i386 output
[[[ … and many more of these lines … ]]]

          /usr/bin/ld: /mnt/host-src/i2n/tmp/test-link/target/debug/build/memchr-44fb1dc86bc067ac/build_script_build-44fb1dc86bc067ac.build_script_build.4gqz6kdo-cgu.0.rcgu.o: file class ELFCLASS64 incompatible with ELFCLASS32
          /usr/bin/ld: final link failed: file in wrong format
          collect2: ld returned 1 exit status


error: aborting due to previous error

error: could not compile `memchr`.

Caused by:
  process didn't exit successfully: `rustc --crate-name build_script_build /datastore/dev/rust/cargo/registry/memchr-2.3.0/build.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C debuginfo=2 --cfg 'feature="default"' --cfg 'feature="std"' -C metadata=44fb1dc86bc067ac -C extra-filename=-44fb1dc86bc067ac --out-dir /mnt/host-src/i2n/tmp/test-link/target/debug/build/memchr-44fb1dc86bc067ac -L dependency=/mnt/host-src/i2n/tmp/test-link/target/debug/deps --cap-lints allow` (exit code: 1)

From the -m64 flag it seems that cargo attempts to compile
the dependencies for the wrong target which must fail since there
is no x86_64 compatible C toolchain in the path. The toolchain
path /datastore/dev/rust/lib/rustlib/x86_64-unknown-linux-gnu
is wrong too as everything 32 bit is located at
/datastore/dev/rust/lib/rustlib/i686-unknown-linux-gnu/.

Where does cargo get those arguments from and how do I change
them?

Btw. even brute forcing the flags doesn’t help:

declare -x LD_RUN_PATH=/opt/rust/lib:/lib:/usr/lib
declare -x LIBRARY_PATH=/opt/rust/lib:/lib/:/usr/lib
declare -x CARGO_BUILD_TARGET=i686-unknown-linux-gnu
declare -x CFLAGS=-m32
declare -x CPPFLAGS=-m32
declare -x CXXFLAGS=-m32
declare -x LDFLAGS="-m32 -L/opt/rust/lib -L/usr/lib -L/lib"
declare -x CC=/usr/bin/i686-redhat-linux-gcc

#strace -f -o /tmp/$(date +%F)_cargo.strace \
cargo build \
    --target i686-unknown-linux-gnu

-> same result. Forcing a target in .cargo/config does not
appear to make a difference either.

  • Rust 1.42.0 compiler: /opt/rust/bin/rustc.
  • Binutils (v2.32) can handle both i686 and x86_64.
  • System compiler: /usr/bin/i686-redhat-linux-gcc (libs in
    /usr/lib, /lib).
1 Like

Even with a --target, cargo often still needs to build some things for the host, namely build scripts, proc-macros, and all of their dependencies. So if you set a global CC, that still needs to be able to link host binaries too. You can set a target-specific cc in a config file, or equivalently with the environment like CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER=...

1 Like

Even with a --target, cargo often still needs to build some
things for the host, namely build scripts, proc-macros, and all
of their dependencies. So if you set a global CC, that still
needs to be able to link host binaries too. You can set a
target-specific cc in a config
file
,
or equivalently with the environment like
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER=...

Thanks, but that doesn’t seem to have any effect. Cargo still
tries to invoke 32-bit GCC with -m64:

25612 execve("/usr/bin/cc", ["cc", "-Wl,--as-needed", "-Wl,-z,noexecstack", "-m64", "-L", "/datastore/dev/rust/lib/rustlib/"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "-o", "/mnt/host-src/intranator/test-li"..., "/mnt/host-src/intranator/test-li"..., "-Wl,--gc-sections", "-pie", "-Wl,-zrelro", "-Wl,-znow", "-nodefaultlibs", "-L", "/mnt/host-src/intranator/test-li"..., ...], 0x7f490c03d740 /* 69 vars */ <unfinished ...>

It is also ignoring the linker path I set in .cargo/config
(or on the command line) for the target and continues with
$PATH lookups. It looks like this atm:

[build]
target = "i686-unknown-linux-gnu"

[target.i686-unknown-linux-gnu]
linker = "/usr/bin/i686-redhat-linux-gcc"
rustflags = ["-C", "link-args=-m32"]

The file is being read though, so I’m starting to believe these
settings have no effect on compilation of dependencies.

Oh, I missed that your system cc is not 64-bit capable -- but you're still using 64-bit rustc? Then I guess you need to specify [target.x86_64-unknown-linux-gnu] linker = "...", or consider just using 32-bit rustc in the first place.

Oh, I missed that your system cc is not 64-bit capable --
but you're still using 64-bit rustc?

Indeed.

                                   Then I guess you need to

specify [target.x86_64-unknown-linux-gnu] linker = "...", or
consider just using 32-bit rustc in the first place.

Setting the x86_64 linker makes cargo use the right gcc but it
keeps on passing it -m64 and, kind of expected, builds 64-bit
rust intermediate binaries (*.rcgu.o) that end up failing to
link.

The reason we skipped on providing a 32-bit only rust toolchain
was that building 32-bit binaries looked trivial thanks to the
cargo configuration. Turns out it’s not so easy after all :wink:

Anyways, I already dug through the cmake docs and came up with a
way of switching to the newer toolchain only for the rust parts.
The final 32-bit static lib gets linked in fine by gcc 4.4 this
way even though cargo produces some 64-bit side effects during
the build. I think I can live with that.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.