tttapa / py-build-cmake

Modern, PEP 517 compliant build backend for creating Python packages with extensions built using CMake.
https://pypi.org/project/py-build-cmake
MIT License
38 stars 6 forks source link

Building cmake project in submodule #11

Closed laggykiller closed 1 year ago

laggykiller commented 1 year ago

Hello, I am trying to create a ctypes library for rlottie. I have created this github repository: https://github.com/laggykiller/rlottie-python

I am trying to build wheel that contains rlottie.dll / librlottie.so / librlottie.dylib, which could then be used by the python module by ctypes.

I have read the documentations already, but it seems like the built files are not added to the wheel.

Part of pyproject.toml (https://github.com/laggykiller/rlottie-python/blob/master/pyproject.toml):

[tool.py-build-cmake.cmake]
build_type = "RelWithDebInfo"
source_path = "."
build_args = ["-j"]
install_components = ["python_module"]

The CMakeLists.txt in the root of my repo (https://github.com/laggykiller/rlottie-python/blob/master/CMakeLists.txt):

cmake_minimum_required(VERSION 3.3)
project(rlottie-python)
find_package(Python3 REQUIRED COMPONENTS Development)

add_subdirectory(rlottie)

# Install the module
install(TARGETS rlottie
        EXCLUDE_FROM_ALL
        LIBRARY     DESTINATION    ${PY_BUILD_CMAKE_MODULE_NAME}
        ARCHIVE     DESTINATION    ${PY_BUILD_CMAKE_MODULE_NAME}
        COMPONENT python_modules)

In theory my CmakeLists.txt should get the rlottie target from rlottie/CMakeLists.txt?

I thought of another way of doing it is by using rlottie/CMakeLists.txt directly and set LIB_INSTALL_DIR to ${PY_BUILD_CMAKE_MODULE_NAME}, but is this possible to do so in pyproject.toml? Does this actually add the built dlls to the wheel?

I am not experienced with C programming and cmake, do you have any suggestions? The documentation is a bit lacking for this kind of project (or I am just a complete fool). Thank you.

tttapa commented 1 year ago

it seems like the built files are not added to the wheel.

This could be caused by the different component names you've used: your pyproject.toml uses python_module, and in CMake, you have python_modules. Also make sure you're actually building the .so/.dll file by setting BUILD_SHARED_LIBS.

[tool.py-build-cmake.cmake]
build_type = "RelWithDebInfo"
source_path = "."
build_args = ["-j"]
install_components = ["python_module"]
cmake_minimum_required(VERSION 3.3)
project(rlottie-python)

set(BUILD_SHARED_LIBS On)
add_subdirectory(rlottie)

# Install the module
install(TARGETS rlottie
        EXCLUDE_FROM_ALL
        LIBRARY     DESTINATION    ${PY_BUILD_CMAKE_MODULE_NAME}
        # You don't need the archives for ctypes
        COMPONENT python_module) # this name should match pyproject.toml

I thought of another way of doing it is by using rlottie/CMakeLists.txt directly and set LIB_INSTALL_DIR to ${PY_BUILD_CMAKE_MODULE_NAME}

Indeed, this should work, with the caveat that it will also install the headers, CMake config and pkg-config files. This seems to be hardcoded in rlottie's CMake script.

[tool.py-build-cmake.cmake] # How to build the CMake project
build_type = "RelWithDebInfo"
source_path = "."
build_args = ["-j"]
cmake_minimum_required(VERSION 3.3)
project(rlottie-python)

set(BUILD_SHARED_LIBS On)
set(LIB_INSTALL_DIR ${PY_BUILD_CMAKE_MODULE_NAME})
add_subdirectory(rlottie)

but is this possible to do so in pyproject.toml?

Sure:

[tool.py-build-cmake.cmake] # How to build the CMake project
build_type = "RelWithDebInfo"
source_path = "rlottie"
build_args = ["-j"]
[tool.py-build-cmake.cmake.options]
BUILD_SHARED_LIBS = "On"
LIB_INSTALL_DIR = "rlottie-python" # Hardcoded, keep in sync with actual name

The documentation is a bit lacking for this kind of project

Do you have specific requests or points that could be improved?

laggykiller commented 1 year ago

Thank you for your help, turns out I am indeed an idiot! After changing python_modules to python_module it does work.

Just one thing to add on, for Windows use RUNTIME, for *nix use LIBRARY (Reference: https://cmake.org/cmake/help/latest/command/install.html)

The resulting CMakeLists.txt should look like this:

cmake_minimum_required(VERSION 3.3)
project(rlottie-python)
find_package(Python3 REQUIRED COMPONENTS Development)

add_subdirectory(rlottie)

# Install the module
if (WIN32)
    install(TARGETS rlottie
            EXCLUDE_FROM_ALL
            RUNTIME DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}
            COMPONENT python_module)
else()
    install(TARGETS rlottie
            EXCLUDE_FROM_ALL
            LIBRARY DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}
            COMPONENT python_module)
endif()

Turns out the solution is elegant and simple!

It would be great if an example project similar to this could be added to this repo? I may help write a pull request for it, but I have to think up with what submodule should be used for demonstration (Another good way is to link my repo for providing example)

tttapa commented 1 year ago

for Windows use RUNTIME, for *nix use LIBRARY

Indeed, although I don't think there's any harm in specifying both, so the if statement is not strictly necessary.

laggykiller commented 1 year ago

If I put RUNTIME later than LIBRARY, windows wheel have dll but not for linux, vice versa 🤔

tttapa commented 1 year ago

Looks like you're right, not sure why that is 🤔