Note: Since asking this question, I discovered later on that python -m pip install -e .
will install the extension to cmod
with .venv/lib/python3.8/site-packages/hello-c-extension.egg-link
pointing to the project in the current directory. I've also switched to a src
layout in later commits and have found https://pythonwheels.com/ to be a great reference for high-quality packages that distribute wheels. However, I'm still curious to know about the behavior of setup.py
subcommands.
As part of some research on manylinux, I am using a toy project to build different platform wheels for a C++ extension module.
It seems that when building and installing locally, I cannot import the C++ extension module if my current directory is the project root directory. This prevents me from running unit tests, among other things. I believe the reason for this is that .
becomes the first component of sys.path
, and so the pure-Python version is picked up while the compiled extension is not.
How can I fix this? Am I running the local build/install correctly?
The package structure looks like this:
$ tree hello-c-extension/
hello-c-extension/
├── LICENSE
├── Makefile
├── README.md
├── cmod
│ ├── __init__.py
│ ├── _cmodule.cc
│ └── pymod.py
├── setup.py
└── tests
├── __init__.py
└── test_cext.py
I also have the project on GitHub; I asked this question as of 29fef5b.
To build/install I use:
cd hello-c-extension
python -m venv .venv
source ./.venv/bin/activate
python -m pip install -U pip wheel setuptools
python setup.py build install
Now, from the current directory, I can import the Python module but not the corresponding extension module. The Python module gets picked up as the one in the current directory, rather than that in site-packages
:
$ python -c 'from cmod import pymod; print(pymod)'
<module 'cmod.pymod' from '/Users/brad/Scripts/python/projects/bsolomon1124/hello-c-extension/cmod/pymod.py'>
$ python -c 'from cmod import _cmod; print(_cmod)'
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: cannot import name '_cmod' from 'cmod' (/Users/brad/Scripts/python/projects/bsolomon1124/hello-c-extension/cmod/__init__.py)
Hackishly deleting the PWD element of sys.path
fixes this:
>>> import sys
>>> sys.path
['', '/Users/brad/.pyenv/versions/3.8.1/lib/python38.zip', '/Users/brad/.pyenv/versions/3.8.1/lib/python3.8', '/Users/brad/.pyenv/versions/3.8.1/lib/python3.8/lib-dynload', '/Users/brad/Scripts/python/projects/bsolomon1124/hello-c-extension/.venv/lib/python3.8/site-packages', '/Users/brad/Scripts/python/projects/bsolomon1124/hello-c-extension/.venv/lib/python3.8/site-packages/hello_c_extension-0.4-py3.8-macosx-10.15-x86_64.egg']
>>> del sys.path[0]
>>> from cmod import _cmod; print(_cmod)
<module 'cmod._cmod' from '/Users/brad/Scripts/python/projects/bsolomon1124/hello-c-extension/.venv/lib/python3.8/site-packages/hello_c_extension-0.4-py3.8-macosx-10.15-x86_64.egg/cmod/_cmod.cpython-38-darwin.so'>
And finally, changing out of the directory makes the problem go away as well:
$ cd ..
$ python -c 'from cmod import _cmod; print(_cmod)'
<module 'cmod._cmod' from '/Users/brad/Scripts/python/projects/bsolomon1124/hello-c-extension/.venv/lib/python3.8/site-packages/hello_c_extension-0.4-py3.8-macosx-10.15-x86_64.egg/cmod/_cmod.cpython-38-darwin.so'>
Is this really ... how it's supposed to work? What would be the proper way to run unit tests for the extension module in this case?
System info:
$ python -V
Python 3.8.1
$ uname -mrsv
Darwin 19.4.0 Darwin Kernel Version 19.4.0: Wed Mar 4 22:28:40 PST 2020; root:xnu-6153.101.6~15/RELEASE_X86_64 x86_64