0

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.

Peter
  • 2,919
  • 1
  • 16
  • 35
  • 1
    You are probably not including extension sources/`CMakeLists.txt` in the source dist. – hoefling Dec 19 '20 at 12:02
  • @hoefling How do I do that? Via an option to `setup`? Which one? – Peter Dec 19 '20 at 12:05
  • 1
    https://setuptools.readthedocs.io/en/latest/userguide/datafiles.html – hoefling Dec 19 '20 at 12:06
  • @hoefling I have thought about using `package_data` but I don't see how to do that here. I.e. by setting `package_dir={"": ".."}` and `package_data={"": "CMakeLists.txt"}`? That doesn't seem to work. – Peter Dec 19 '20 at 12:15
  • 1
    You can also use the code from [my answer](https://stackoverflow.com/a/48015772/2650249) which will just work. – hoefling Dec 19 '20 at 12:20

0 Answers0