I'm working on a C++/Python project with the following structure:
foo
├── CMakeLists.txt
├── include
├── source
└── python
├── foo
│ ├── _foo_py.py
│ └── __init__.py
├── setup.py
└── source
├── CMakeLists.txt
└── _foo_cpp.cpp
foo/source
and foo/include
contain C++ source files and foo/python/source/_foo_cpp.cpp
contains pybind11 wrapper code for this C++ code. Running setup.py
is supposed to build the C++ code (by running CMake), create a _foo_cpp
Python module in the form of a shared object and integrate it with the Python code in _foo_py.py
. I.e. I want to be able to simply call python setup.py install
from foo/python
to install the foo
module to my system. I'm currently using a CMake extension class in setup.py
to make this work:
class CMakeExtension(Extension):
def __init__(self, name, sourcedir):
Extension.__init__(self, name, sources=[])
self.sourcedir = os.path.abspath(sourcedir)
class CMakeBuild(build_ext):
def run(self):
try:
subprocess.check_output(['cmake', '--version'])
except OSError:
raise RuntimeError("cmake command must be available")
for ext in self.extensions:
self.build_extension(ext)
def build_extension(self, ext):
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
self._setup(ext)
self._build(ext)
def _setup(self, ext):
cmake_cmd = [
'cmake',
ext.sourcedir,
]
subprocess.check_call(cmake_cmd, cwd=self.build_temp)
def _build(self, ext):
cmake_build_cmd = [
'cmake',
'--build', '.',
]
subprocess.check_call(cmake_build_cmd, cwd=self.build_temp)
The problem arises when I try to directly call pip
in foo/python
, e.g. like this:
pip wheel -w wheelhouse --no-deps .
It seems that before running the code in setup.py
, pip
copies the content of the working directory into a temporary directory. This obviously doesn't include the C++ code and the top-level CMakeLists.txt
. That in turn causes CMakeBuild._setup
to fail because there is seemingly no way to obtain a path to the foo
root directory from inside setup.py
after it has been copied to another location by pip
.
Is there anything I can do to make this setup work with both python
and pip
? I have seen some approaches that first run cmake
to generate a setup.py
from a setup.py.in
to inject package version, root directory path etc. but I would like to avoid this and have setup.py
call cmake
instead of the other way around.