distutils
distutils copied to clipboard
[BUG] self.linker_ex: TypeError: 'NoneType' object is not subscriptable
setuptools version
setuptools==72.2.0
Python version
Python 3.8.16 (PyPy 7.3.11)
OS
Ubuntu 20.04
Additional environment information
This issue occurs only when I run setuptools with PyPy; it works fine with CPython. Downgrading to setuptools version 72.1.0 resolves the issue for both PyPy and CPython.
Description
When building a Cython extension that depends on a shared library, setuptools triggers a TypeError during the linking process.
Expected behavior
setuptools doesn't fail.
How to Reproduce
Create a file named hello_world.cpp:
// hello_world.cpp
#include <iostream>
extern "C" void print_hello() {
std::cout << "Hello, World!" << std::endl;
}
Create a file named hello_world.h:
// hello_world.h
#ifndef HELLO_WORLD_H
#define HELLO_WORLD_H
extern "C" void print_hello();
#endif // HELLO_WORLD_H
On Linux, compile this into a shared library using:
g++ -c -fPIC hello_world.cpp -o hello_world.o
g++ -shared hello_world.o -o libhello_world.so
Create a file named hello.pyx:
# hello.pyx
cdef extern from "hello_world.h":
void print_hello()
def call_print_hello():
print_hello()
Create a setup.py file to build the Cython extension:
# setup.py
from setuptools import setup, Extension
from Cython.Build import cythonize
extensions = [
Extension(
"hello",
["hello.pyx"],
language="c++",
include_dirs=["."],
library_dirs=["."],
libraries=["hello_world"],
extra_compile_args=["-std=c++11"]
)
]
setup(
name="hello",
ext_modules=cythonize(extensions),
)
Run the following command in the terminal:
python setup.py build_ext --inplace
Output
Traceback (most recent call last):
File "setup.py", line 17, in <module>
setup(
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/__init__.py", line 108, in setup
return distutils.core.setup(**attrs)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/core.py", line 184, in setup
return run_commands(dist)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/core.py", line 200, in run_commands
dist.run_commands()
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/dist.py", line 964, in run_commands
self.run_command(cmd)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/dist.py", line 945, in run_command
super().run_command(command)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/dist.py", line 983, in run_command
cmd_obj.run()
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/command/build_ext.py", line 93, in run
_build_ext.run(self)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/command/build_ext.py", line 359, in run
self.build_extensions()
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/command/build_ext.py", line 476, in build_extensions
self._build_extensions_serial()
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/command/build_ext.py", line 502, in _build_extensions_serial
self.build_extension(ext)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/command/build_ext.py", line 254, in build_extension
_build_ext.build_extension(self, ext)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/Cython/Distutils/build_ext.py", line 135, in build_extension
super(build_ext, self).build_extension(ext)
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/command/build_ext.py", line 581, in build_extension
self.compiler.link_shared_object(
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/ccompiler.py", line 757, in link_shared_object
self.link(
File "/home/daniel/.pyenv/versions/pypy3.8-7.3.11/lib/pypy3.8/site-packages/setuptools/_distutils/unixccompiler.py", line 269, in link
self.linker_exe
TypeError: 'NoneType' object is not subscriptable (key slice(None, None, None))
We are seeing the same problem with building Matplotlib wheels, though not directly. It's actually failing on kiwisolver, but this is a bit annoying to work around, since the setuptools install is under cibuildwheel's test command + build isolation.
A bisect points to 0ab156c3e12054503a0dac0ce172e241262881f4, which is not too surprising, I think. Bisecting distutils itself points to pypa/distutils@2c937116cc0dcd9b26b6070e89a3dc5dcbedc2ae
It looks like PyPy only just added LDCXXSHARED (and there's been no releases with it) but the fetch from sysconfig to distutils's linker_so_cxx does not have any handling of that. Something simple like this might work:
diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py
index fbdd5d73..19ea6aa0 100644
--- a/distutils/sysconfig.py
+++ b/distutils/sysconfig.py
@@ -321,6 +321,9 @@ def customize_compiler(compiler):
)
cxxflags = cflags
+ if ldcxxshared is None:
+ # Older versions of PyPy do not set LDCXXSHARED.
+ ldcxxshared = ldshared
if 'CC' in os.environ:
newcc = os.environ['CC']
Actually, looking at the commit again, it appears that CygwinCCompiler and UnixCCompiler both have a default for linker_so_cxx, but it's customize_compiler that overwrites it. So instead of trying to set the default in customize_compiler, it should just not set it, perhaps:
diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py
index fbdd5d73..29a2ba36 100644
--- a/distutils/sysconfig.py
+++ b/distutils/sysconfig.py
@@ -362,11 +362,13 @@ def customize_compiler(compiler):
compiler_cxx=cxx_cmd,
compiler_so_cxx=cxx_cmd + ' ' + ccshared,
linker_so=ldshared,
- linker_so_cxx=ldcxxshared,
linker_exe=cc,
linker_exe_cxx=cxx,
archiver=archiver,
)
+ if ldcxxshared is not None:
+ # Older versions of PyPy do not set LDCXXSHARED.
+ compile.set_executable('linker_so_cxx', ldcxxshared)
if 'RANLIB' in os.environ and compiler.executables.get('ranlib', None):
compiler.set_executables(ranlib=os.environ['RANLIB'])
Thank you @QuLogic for the investigation.
@jaraco , @lazka do you think this patch is viable in the pypa/distutils repository? Should we move this issue there?
The patches above need a few more None checks for CXXFLAGS and LDCXXSHARED cases, but yeah, moving to distutils sounds good to me.
I wonder why CI didn't catch this.
See #228 where this behavior was added. That PR and the one that preceded it were tricky to get right, especially due to the nuances of manipulating the sysconfig variables and the different locations where those variables are defined.
I see now how that PR caused a regression. Prior to this change, distutils and setuptools would treat C++ like C, only honoring the C configs and environment and disregarding the C++ settings. With the change, it now honors the C++ settings for C++ extensions, but there are some environments that don't yet have C++ settings configured. In that case, we want distutils to fall back to the old behavior (C settings).
I wonder why CI didn't catch this.
We should definitely determine the answer. If the current tests don't cover the condition (and it seems they don't), we'll want to manifest a test that does capture the missed expectation.
Looking at the original PR, test_customize_compiler does not set LDCXXSHARED, but it only checks compiler_cxx, and none of the new compiler_so_cxx, linker_so_cxx, linker_exe_cxx, so it neither confirms that the environment variable works, nor confirms that the fallback works.
It looks like PyPy only just added
LDCXXSHARED(and there's been no releases with it)
This has now been released in last week's PyPy3.10 v7.3.17:
- https://pypy.org/posts/2024/08/pypy-v7317-release.html
- https://github.com/pypy/pypy/releases/tag/release-pypy3.10-v7.3.17
GitHub Actions is still on 7.3.16:
- https://github.com/ultrajson/ultrajson/actions/runs/10651820147/job/29524844616#step:3:14
Reported: https://github.com/actions/setup-python/issues/936
Could you add check-latest: true to the setup-python action options? I don't know how expensive it is, in any case you can comment it out once the image updates.
Thanks, that does force 7.3.17 to be installed and now it passes for Ubuntu but fails for macOS with a different error:
ld: unknown options: -Bsymbolic-function
https://github.com/hugovk/ultrajson/actions/runs/10676841128
I wonder where that is coming from. As far as I can tell, it is not here (distutils) nor in PyPy. Is it an xcode default?
So is there a solution to this?
PyPy has quite a few releases since it added LDCXXSHARED. Is this still happening?
PyPy has quite a few releases since it added
LDCXXSHARED. Is this still happening?
Based on the linked commits above as recent as Feb 4, it sounds like it may still be happening.
I wonder why CI didn't catch this.
We should definitely determine the answer. If the current tests don't cover the condition (and it seems they don't), we'll want to manifest a test that does capture the missed expectation.
Looking at the original PR,
test_customize_compilerdoes not setLDCXXSHARED, but it only checkscompiler_cxx, and none of the newcompiler_so_cxx,linker_so_cxx,linker_exe_cxx, so it neither confirms that the environment variable works, nor confirms that the fallback works.
At the very least, it'd be great if someone could craft a patch to the tests that will capture the missed expectation, so that distutils' own tests can confirm that the issue is fixed and won't regress.
Perhaps @QuLogic or @sciyoshi would be willing to develop such a test?
I don't understand the problem: what is missing in PyPy?
This commit was intended to fix this issue on PyPy but it was backed out due to issues on OSX I believe. The relevant comment and discussion is here: https://github.com/pypa/distutils/pull/228#issuecomment-2265478504
Specifically https://github.com/pypa/distutils/pull/228#issuecomment-2265537941 where we thought this was an acceptable limitation (C++ on PyPy), but I suppose that's not the case.
Sorry, I missed the earlier discussion in this issue referencing these issues already.
I can confirm however that the issue no longer occurs with newer versions PyPy (tested PyPy 3.10 v7.3.19).
@RuABraun could you provide more information since this should have been solved. What version of PyPy are you using, what does your invocation look like, what platform are you using?
Thanks, that does force 7.3.17 to be installed and now it passes for Ubuntu but fails for macOS with a different error:
ld: unknown options: -Bsymbolic-function
This is still happening on macOS with PyPy 7.3.19 (Python 3.11.11):
- https://github.com/ultrajson/ultrajson/actions/runs/14185861876/job/39740941480?pr=660
- https://github.com/ultrajson/ultrajson/pull/660
I changed the values of LD*SHARED for macOS on pypy3.11 in the latest HEAD to use clang-compatible parameters.
It sounds as if there's nothing more to do here. I'd still love for distutils to have a test that captures the failure. If someone wishes to author one, I'd readily accept it.