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

[BUG] self.linker_ex: TypeError: 'NoneType' object is not subscriptable #283

Open
daniel-larraz opened this issue Aug 15, 2024 · 11 comments
Labels
bug Something isn't working

Comments

@daniel-larraz
Copy link

daniel-larraz commented Aug 15, 2024

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))
@daniel-larraz daniel-larraz added bug Something isn't working Needs Triage labels Aug 15, 2024
@QuLogic
Copy link

QuLogic commented Aug 16, 2024

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 2c93711

@QuLogic
Copy link

QuLogic commented Aug 17, 2024

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']

@QuLogic
Copy link

QuLogic commented Aug 17, 2024

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'])

QuLogic referenced this issue in QuLogic/matplotlib Aug 17, 2024
Due to https://github.com/pypa/setuptools/issues/4571, kiwisolver fails
to build on PyPy. Until kiwisolver has PyPy 3.10 wheels
(nucleic/kiwi#182), we should avoid the buggy
setuptools.
QuLogic referenced this issue in QuLogic/matplotlib Aug 17, 2024
Due to https://github.com/pypa/setuptools/issues/4571, kiwisolver fails
to build on PyPy. Until kiwisolver has PyPy 3.10 wheels
(nucleic/kiwi#182), we should avoid the buggy
setuptools.
@abravalheri
Copy link
Contributor

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?

@lazka
Copy link
Contributor

lazka commented Aug 17, 2024

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.

esuldin referenced this issue in esuldin/pyitt Aug 18, 2024
setuptools version 72.2.0 contains the change that does not allow to
build C++ native extension for PyPy, because it does not provide the
LDCXXSHARED configuration variable:
https://github.com/pypa/setuptools/issues/4571
@jaraco jaraco transferred this issue from pypa/setuptools Aug 18, 2024
@jaraco
Copy link
Member

jaraco commented Aug 18, 2024

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.

xzkostyan added a commit to mymarilyn/clickhouse-driver that referenced this issue Aug 19, 2024
QuLogic referenced this issue in pikepdf/pikepdf Aug 22, 2024
…f pp3.9

Error is:
     File "/tmp/build-env-xb6cqppy/lib/pypy3.9/site-packages/setuptools/_distutils/unixccompiler.py", line 268, in link
      linker = (
  TypeError: 'NoneType' object is not subscriptable (key slice(None, None, None))

Unclear if this is due to pybind11 needing an adjustment for setuptools 72
https://github.com/pypa/setuptools/blob/main/NEWS.rst
or cibuildwheel needs something patched.
@QuLogic
Copy link

QuLogic commented Aug 22, 2024

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.

@hugovk
Copy link
Contributor

hugovk commented Sep 2, 2024

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:

GitHub Actions is still on 7.3.16:

Reported: actions/setup-python#936

@mattip
Copy link
Contributor

mattip commented Sep 2, 2024

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.

@hugovk
Copy link
Contributor

hugovk commented Sep 3, 2024

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

@mattip
Copy link
Contributor

mattip commented Sep 3, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

7 participants