Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix finding headers when cross compiling #35

Closed
wants to merge 1 commit into from

Conversation

lopsided98
Copy link
Contributor

When cross-compiling extension modules using build_ext, get_python_inc() may be called to return the path to Python's headers. However, it uses the sys.prefix or sys.exec_prefix of the build Python, which returns incorrect paths when cross-compiling (paths pointing to build system headers).

To fix this, we use the INCLUDEPY and CONFINCLUDEPY conf variables, which can be configured to point at host Python by setting _PYTHON_SYSCONFIGDATA_NAME. The existing behavior is maintained on non-POSIX platforms or if a prefix is manually specified.

This was originally submitted to stdlib distutils as python/cpython#22440, but unfortunately they are no longer accepting patches to it even though setuptools still uses stdlib distutils by default.

I tested this with the netifaces package, which compiles an extension module and fails with the following error if it is cross-compiled from x86_64 to armv7l without this patch, due to the word size difference:

  In file included from /nix/store/c84d97qz2x7yfaijcjwapvrnbwlkljs0-python3-3.8.7/include/python3.8/Python.h:63,
                   from netifaces.c:1:
  /nix/store/c84d97qz2x7yfaijcjwapvrnbwlkljs0-python3-3.8.7/include/python3.8/pyport.h:726:2: error: #error "LONG_BIT definition appears wrong for platform (bad gcc/glibc config?)."
    726 | #error "LONG_BIT definition appears wrong for platform (bad gcc/glibc config?)."
        |  ^~~~~
  error: command 'armv6l-unknown-linux-gnueabihf-gcc' failed with exit status 1

After applying this patch and setting _PYTHON_SYSCONFIGDATA_NAME and SETUPTOOLS_USE_DISTUTILS appropriately, the build succeeds.

The stdlib distutils version of this patch is currently applied in nixpkgs (NixOS/nixpkgs#98915) and we have not observed any breakage.

When cross-compiling third-party extensions, get_python_inc() may be called to
return the path to Python's headers. However, it uses the sys.prefix or
sys.exec_prefix of the build Python, which returns incorrect paths when
cross-compiling (paths pointing to build system headers).

To fix this, we use the INCLUDEPY and CONFINCLUDEPY conf variables, which can
be configured to point at host Python by setting _PYTHON_SYSCONFIGDATA_NAME.
The existing behavior is maintained on non-POSIX platforms or if a prefix is
manually specified.
@jaraco
Copy link
Member

jaraco commented Apr 24, 2021

Thanks for the contrib.

I don't have a lot of experience with the sysconfig module. Right now, there's a lot of work going on right now to refactor sysconfig to enable better configurability by different platforms, so it's possible this change will conflict with some of those efforts.

Is there a test that could be added to this PR that would help capture the missed expectation (and thus fail before and pass after the patch)?

@jaraco jaraco added the stale label Jul 4, 2021
@jaraco jaraco closed this Jul 4, 2021
@lopsided98
Copy link
Contributor Author

Would you be willing to take another look at this patch? I have rebased it and reworked it a bit (not visible unless the PR is reopened). I will also explain more thoroughly how we (nixpkgs) use this patch to enable cross-compilation of Python packages with extension modules, as well as the challenges involved in developing an automated test.

We use the following techniques to make cross-compilation work:

  • Apply this patch to either stdlib distutils (before setuptools 60) or the bundled distutils in setuptools.
  • Add the directory containing the _sysconfigdata module for the host platform Python installation to PYTHONPATH, so that it can be imported from build platform Python.
  • Set _PYTHON_HOST_PLATFORM to the correct value for the host platform (e.g. linux-armv7l)
  • Set _PYTHON_SYSCONFIGDATA_NAME to the appropriate value for the host platform (e.g. _sysconfigdata__linux_arm-linux-gnueabihf).

setup.py is executed using build platform Python (by necessity, since the build machine can't normally run host platform binaries), but the sysconfig module returns data from the host platform Python installation, due to _PYTHON_SYSCONFIGDATA_NAME.

If the package contains extension modules, eventually distutils.sysconfig.get_python_inc() will be called to get the location of the Python headers. Without this patch, the build platform headers will be returned, since get_python_inc() returns paths relative to sys.base_prefix or sys.base_exec_prefix. This causes problems when the build platform word length doesn't match the host platform, resulting in errors like the following:

In file included from /nix/store/01kia41csjia67pry1rv828i9pvnnqfq-python3-3.9.12/include/python3.9/Python.h:50,
                 from btrfsutilpy.h:27,
                 from error.c:20:
/nix/store/01kia41csjia67pry1rv828i9pvnnqfq-python3-3.9.12/include/python3.9/pyport.h:741:2: error: #error "LONG_BIT definition appears wrong for platform (bad gcc/glibc config?)."
  741 | #error "LONG_BIT definition appears wrong for platform (bad gcc/glibc config?)."
      |  ^~~~~

This patch avoids this problem by relying on sysconfig to provide the include paths. With _PYTHON_SYSCONFIGDATA_NAME set appropriately, the include directories will be returned from host platform Python.

The latest version of the patch will always use sysconfig on posix if the relevant config variables are defined, otherwise it will fall back to the current behavior.

I have been having trouble coming up with an effective and simple automated test due to the amount of infrastructure required. I think you would need multiple Python installations in order to demonstrate how _PYTHON_SYSCONFIGDATA_NAME can affect the return value of get_python_inc(). IMO, the most important thing is ensuring that existing (non-cross) use cases are not affected by this change.

@jaraco
Copy link
Member

jaraco commented May 19, 2022

Yes. I'd be happy to revisit the patch. Thanks for all the extra detail. I'm currently unable to reopen the pull request due to the branch having been forced pushed or recreated:

image

If you wish to push the old code, I can re-open the PR and then you can force push again... or you can open a new PR.

@lopsided98
Copy link
Contributor Author

I opened #145

jaraco added a commit that referenced this pull request Jul 1, 2023
* Use `extend-ignore` in flake8 config

This option allows to add extra ignored rules to the default list
instead of replacing it.

The default exclusions are: E121, E123, E126, E226, E24, E704,
W503 and W504.

Fixes #28.

Refs:
* https://github.com/pypa/setuptools/pull/2486/files#r541943356
* https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-extend-ignore
*
https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-ignore

* Enable complexity limit. Fixes jaraco/skeleton#34.

* Replace pep517.build with build (#37)

* Replace pep517.build with build

Resolves #30

* Prefer simple usage

Co-authored-by: Jason R. Coombs <[email protected]>

* Use license_files instead of license_file in meta (#35)

Singular `license_file` is deprecated since wheel v0.32.0.

Refs:
* https://wheel.readthedocs.io/en/stable/news.html
* https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file

Co-authored-by: Jason R. Coombs <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants