conda-forge / conda-forge.github.io

The conda-forge website.
https://conda-forge.org
BSD 3-Clause "New" or "Revised" License
132 stars 279 forks source link

best practice for including compiler version in abi for dependencies (pybind11/nanobind) #2326

Open minrk opened 1 month ago

minrk commented 1 month ago

Your question:

The problem

For packages built with pybind11 and nanobind, the major version of the C++ compiler is part of the ABI. That means any package built with nanobind that links to another package via its nanobind API has a runtime requirement that both were built with the same major version of the compiler. This is not currently handled by the pybind11-abi metapackage or other compiler run_exports.

I don't think it comes up very often, but it does in the fenics stack. For fenics, I've added a fenics-basix-nanobind-abi package with a custom version that includes the nanobind and cxx_compiler versions. In the past, I had the same that bundled the pybind11-abi version and compiler version. I've only made one of these because everything in fenics depends on basix, but technically every package build with nanobind (that might be linked against in host) should have one. But this is a general problem, if not a common one. pybind11 has a pybind11-abi package, and nanobind will soon as well. But this is only half a solution, because it doesn't take the build-time compiler version into account, which is also part of the ABI in packages built with either pybind11 or nanobind.

My fenics-basix-nanobind-abi approach also doesn't solve the problem as well as I'd like because downstream packages build just fine since the compiler version is not actually restricted in the build env, but they won't import. that means e.g. cross-compiled outputs pass all CI when they are completely broken, when really the build should have been prevented in the first place due to the compiler version conflict.

Also, I've only dealt with this on linux/mac and don't actually know what needs to be restricted on Windows.

Question

I've struggled to come up with a general solution, but it seems like having nanobind or pybind11 in host dependencies should:

  1. restrict the C++ compiler version at build time when in host requirements, and
  2. restrict install-time packages to those built with the same C++ compiler

One hard part is that pybind11/nanobind packages are host dependencies, so can't easily restrict the cxx compiler version in the build environment. Is there a package in the host env that could be used as a proxy? Or should we be adding a metapackage to the build env to keep things in sync?

Does anyone have a recommendation/experience for a best practice for ensuring ABI compatibility that is sensitive to the compiler version? Is defining one of these abi metapackage outputs for every nanobind-built package really the way to go?

Possible solution

My only idea so far for a general solution is to update the pybind11/nanobind-abi packages (or separately add nanobind-cxx-abi) to generate a version for each compiler version with run_constrains on the compiler version and nanobind and strong run_exports on itself.

so e.g. nanobind-cxx-abi 0.13.12 gxx_* would have:

where:

and would look something like:

run_exports:
  strong:
    - {{ pin_subpackage('nanobind-cxx-abi', max_pin='x.x.x.x') }} {{ cxx_compiler }}_*
run_constrained:
  # constrain compiler version (only works if it's in the build env)
  - {{ cxx_compiler }}_impl_{{ target_platform }} {{ cxx_compiler_version }}.*
  # constrain nanobind abi (host env)
  - nanobind-abi {{ nanobind_internals_version }}
  # another compiler constraint that would work on the host env? libstdcxx_devel?

and then packages make sure this is the same version in both the build and host requirements. But as convoluted as that is, I'm not even sure it would work.

traversaro commented 1 month ago

xref related issue on pybind11 side:

minrk commented 1 month ago

It looks like most of the discussion is around "try to get these packages to not include the compiler version in the abi" rather than accepting the current behavior and expressing the dependencies that they have.

traversaro commented 1 month ago

It looks like most of the discussion is around "try to get these packages to not include the compiler version in the abi" rather than accepting the current behavior and expressing the dependencies that they have.

Indeed, I cross-linked exactly to make sure that people looking to that discussions could find also this related discussion.

carterbox commented 1 month ago

pybind11 and nanobind are both header-only packages, right? As header-only packages, "host" doesn't really have any significance for them since the same artifacts are used regardless of the host/target platform.

They could be used as build dependencies instead of host dependencies so that they can directly interact with the compiler packages. If the solution requires moving these packages to the build section, we could add a linting rule / migration to the channel to have new builds move these packages there.

beckermr commented 1 month ago

The issue here is pass items at runtime between two different libraries compiled with these packages. The layout of objects in memory can differ and cause ABI issues.