Skip to content

Native aarch64 rebuild breaks cross-aarch64 C++ extension linking downstream (LDCXXSHARED regression) #880

Description

@leofang

Downstream cross-aarch64 recipes with a C++ Python extension (e.g. numba-cuda, libnvshmem) started failing at link time after python-feedstock's aarch64 build was flipped from cross to native. The compiler stack and the recipe-side sed are both unchanged; what changed is what CPython's own configure step emits into the sysconfigdata.

When it landed

The "go native for arm builds" change removed linux_aarch64: linux_64 from conda-forge.yml, landing on 2026-06-11 to 2026-06-12 across every currently-live branch except 3.12:

Each PR bundled the flip with an unrelated main change (version bump or openssl backport); the flip itself is a one-liner in conda-forge.yml.

Symptom (downstream cross-aarch64 link step)

$PREFIX/compiler_compat/ld: $BUILD_PREFIX/bin/../libexec/gcc/aarch64-conda-linux-gnu/14.3.0/liblto_plugin.so: error loading plugin: ... cannot open shared object file: No such file or directory

Misleading error — the actual issue is the aarch64 ld shim in $PREFIX/compiler_compat/ being handed an x86_64 LTO plugin path via --plugin.

Bisect (numba-cuda-feedstock main, last-green vs first-fail)

  • gcc_impl_linux-aarch64 / binutils_impl_linux-aarch64 / ld_impl_linux-aarch64: identical (14.3.0=h77f067d_19 / 2.45.1=default_hc5fa074_102 / 2.45.1=default_h27e1c4c_102)
  • python (linux-aarch64): 3.10.20=h28be5d3_0_cpython3.10.20=h4f76b5d_1_cpython

Extracting _sysconfigdata_aarch64_conda_linux_gnu.py (the "raw configure" variant, saved before the recipe-side sed) from each package:

  • h28be5d3_0 (cross-built): 'CXX': 'aarch64-conda-linux-gnu-c++'
  • h4f76b5d_1 (native-built): 'CXX': 'aarch64-conda-linux-gnu-c++ -pthread'

LDCXXSHARED = "$(CXX) -shared", so it picks up the same -pthread on native. Then recipe/build_base.sh#L453-L456:

sed -i.bak "s@-pthread@-pthread -B $PREFIX/compiler_compat@g" sysconfigfile

turns the default sysconfigdata's LDCXXSHARED into 'g++ -pthread -B $PREFIX/compiler_compat -shared'. Setuptools uses LDCXXSHARED verbatim for C++ extension links, so -B $PREFIX/compiler_compat reaches the cross-toolchain link line and routes ld through the aarch64 shim. On a linux-64 build host driving an x86_64 GCC, the aarch64 ld can't handle the x86_64 LTO plugin GCC hands it via --plugin, and the link fails.

Why C-only cross-builds haven't been failing the same way

LDSHARED has always had -B $PREFIX/compiler_compat in it too, but patch 0019-Fix-LDSHARED-when-CC-is-overriden-on-Linux-too.patch makes distutils' customize_compiler strip the sysconfig CC prefix off LDSHARED and replace it with os.environ['CC']. Since LDSHARED starts with exactly the sysconfig CC string ('gcc -pthread -B $PREFIX/compiler_compat'), the entire -pthread -B compiler_compat chunk is consumed along with gcc, leaving only -shared -Wl,... in the resulting command. No equivalent exists for LDCXXSHARED, so it's used as-is.

Why 3.12 isn't affected

The 3.12 branch of this feedstock still has linux_aarch64: linux_64 in conda-forge.yml — it wasn't included in the "go native" flip. Correspondingly, only linux_aarch64_python3.12 passes in numba-cuda-feedstock run 28638905979; all other Python versions fail on aarch64.

Confirmed affected feedstocks

Suggested fixes

  1. Extend patch 0019 to also strip the sysconfig CXX prefix off LDCXXSHARED — symmetric with the C side. Preserves the sed's intent for native/system-gcc users.
  2. Or scope the sed itself away from CXX/LDCXXSHARED so -pthread -B compiler_compat never enters those vars.

Happy to test either against numba-cuda-feedstock's CI once a rebuild is available.

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