Skip to main content

Seamless integration of Cmake build system to setuptools/distutils

Project description

Setuptools extensions for CMake: Seamless integration of Cmake build system to setuptools

This Python package provides an extension to setuptools to integrate CMake into setuptools workflow. Specifically, CMake build tool is responsible to build/install full Python distribution package with binary extensions then setuptools follows up to package up the ready-to-go files for binary distribution (bdist_wheel/bdist_egg/etc) or the CMake source directory for source distribution (sdist).

Features

  • setup() Wrapper: cmaketools.setup() wraps setuptools.setup() to provide one-stop setup() call for both CMake and setupstools.
  • Source Distributable: cmaketools let you create a pip-installable source distribution via Setuptools' sdist command. This enables the use of tox to perform multi-environment testing.
  • Automatic Source Content Detection: By taking advantage of the source directory structure imposed by CMake project, setup.py's critical keywords: package_dir, packages, package_data, and ext_modules.
  • Source File Protection: Neither CMake nor Python setuptools modify the content of the source subdirectory under any command.
  • Git Submodule Aware: If a project contains git submodules, the submodules will be automatically cloned during pip installation and the pinned commit of each submodule will be checked out before build.
  • Support for CMake Command-Line Options: The most of the CMake command line options are made available as options to the build_ext command of setuptools. For example, python setup.py build_ext -GNinja will build the CMake project with Ninja build system.
  • Integaration of Native Code Tests: CMake ships with a test driver program, called ctest. It could be called directly from Python via cmaketools.cmakeutil.ctest().

Examples

You can experiment cmaketools with different Python/native interfaces availeble from following GitHub templates:

Source Directory Structure

The structure of the source directory and placements of CMakeLists.txt are vital to minimize potential packaging complications. Here are some key tips in sturcturing the source directory:

  • Source Directory (src) corresponds to the root package (or Lib\site-packages in Python directory). It could be named arbitrarily so long as it is assigned to src_dir attribute of CMakeBuilder.
  • Package Directory Source directory and its subdirectries with __init__.py file are included in packages setup argument.
  • Pure Python Modules Place all .py module files where they belong within the package structure.
  • Binary Extension Module To define a binary module, create a subdirectory under a package folder it belongs to. In the example, src/mypkg/example_module is one such directory, and it defines mypkg.example_module binary module. Each binary module directory should contain CMakeLists.txt file which defines the library target. For example, the CMakeLists.txt file in module directory shall call pybind11_add_module to include a pybind11-based module to the build project. This is a requirement for the auto-configuration of ext_modules setup argument.
  • Additional Files Any "owned" additional files needed to build the binary modules or to be used by the package shall be placed somewhere in the source directory as it is the directory packaged in sdist (other than setup files).
  • 3rd-Pary Files Script CMake to install them to their final in-package location to keep your package platform agnostic. This can be done via git submodules or CMake file(DOWNLOAD <url> <file> ...) command, then build it if necessary and install the files relative to CMAKE_INSTALL_PREFIX.

CMakeLists.txt Authoring Tips

First, to learn how to author CMake scripts, visit Official CMake Tutorial.

The CMake integration relies on CMake's ability to traverse directory hierarchies, i.e., to encapsulate the build process of each directory via its CMakeLists.txt script and traverse directries. Some script snippets are repetitive and reusable as described below.

Here are general tips:

  • In general, CMakeLists.txt is expected in the source directory and its (sub)directories (possibly excluding resource/asset directories). Parent CMakeLists.txt must call add_subdirectory() for each of its subdirectories.

  • Base Source Directory shall define a SRC_DIR variable by

    set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    

    so relative paths of subdirectories can be evaluated later.

  • Package Directories with pure Python modules must contain install(FILES <file>...) command to copy all .py files to the install target folder (typically dist/<package_name>):

    file(RELATIVE_PATH DST_DIR ${SRC_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
    file(GLOB PYFILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.py")
    install(FILES ${PYFILES} DESTINATION ${DST_DIR} COMPONENT "PY")
    

    Note COMPONENT "PY" designation in install. This lets setuptools's build_py to run CMake to install these files (and package_data files).

  • External Module Directories runs add_library(<name> SHARED | MODULE ...) command either directly or indirectly. Here, it is imperative to set name of the library target to match its directory name. Then the target is copied to the final destination with install(TARGETS <target>...) command.

    # match target name to folder name
    get_filename_component(TARGET ${CMAKE_CURRENT_SOURCE_DIR} NAME)
    
    # build commands
    add_library(${TARGET} ...)
    set_target_properties(${TARGET} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}")
    set_target_properties(${TARGET} PROPERTIES SUFFIX "${PYTHON_MODULE_EXTENSION}")
    # ... more build commands to follow
    
    # install commands
    get_filename_component(CURRENT_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
    if(${SRC_DIR} STREQUAL ${CURRENT_SRC_DIR})
      set(DST_DIR ".") # if parent is the base source folder
    else()
      file(RELATIVE_PATH DST_DIR ${SRC_DIR} ${CURRENT_SRC_DIR})
    endif()
    install(TARGETS ${TARGET} DESTINATION ${DST_DIR} COMPONENT "EXT")
    

    Here we register the install as EXT component so build_ext will only copy external modules to their final locations.

  • Own Package Data Files are handled in a similar fashion as the pure Python modules with install(FILES <file>...) command as PY component.

    # to install a package data file 'data.txt'
    file(RELATIVE_PATH DST_DIR ${SRC_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
    install(FILES "data.txt" DESTINATION ${DST_DIR} COMPONENT "PY")
    
  • 3rd-Party Package Data Files are a bit trickier. The most intuitive way perhaps is to call the install command from the source folder, which matches the folder where the 3rd-party file is placed in the package. For example, suppose this skeltal directory model:

    # After 'cmake --install build'
    project-root/
    ├── build/
    |   └── lib/
    |       └── 3rd-party-tool/
    |           └── libtool.dll # <=original
    ├── dist/
    |   └── mypkg/
    |       └── lib/
    |           └── libtool.dll # <=distro-ready file (the install destination)
    ├── lib/
    |   └── 3rd-party-tool/ # lib source files in here to be built
    └── src/
        └── mypkg/
            └── lib/
                └── CMakeLists.txt # <=issues install command in this file
    

    The source files of a 3rd-party library is included to the project via git submodule in lib/3rd-party-tool/ and when built let's assume its DLL (assuming Windows) file will be found at build/lib/3rd-party-tool/libtool.dll. We want this DLL file to be placed in lib folder of the Python package, which means CMake must install (copy) libtool.dll to dist/mypkg/lib/libtool.dll. The install command shall be issued by src/mypkg/lib/CMakeLists.txt even if src/mypkg/lib/ would otherwise be empty.

    # to install a package data file
    SET(DLL_NAME "libtool.dll")
    SET(DLL_PATH "${CMAKE_BINARY_DIR}/lib/3rd-party-tool/${DLL_NAME}")
    file(RELATIVE_PATH DST_DIR ${SRC_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
    install(FILES ${DLL_PATH} DESTINATION ${DST_DIR} COMPONENT "PY")
    

    Note: Typically you can construct CMake variable via libarary's CMake variables rather than hard-coding the DLL_PATH as done above.

  • Project Root CMakeLists.txt defines general configurations (such as finding dependent libraries and setting up tests) of the build project. There are a couple things could be configured here to improve the CMake/Setuptools co-operation.

    • Set default install path to be dist so CMake by default installs to the same dist directory location as setuptools:

      if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
          set (CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/dist" CACHE PATH "default install path" FORCE )
      endif()
      

build_ext Command Options for cmaketools-based setup.py

The build_ext command options are completely changed to accomodate CMake command-line options. Here is the output of python setup.py --help build_ext

Common commands: (see '--help-commands' for more)

  setup.py build      will build the package underneath 'build/'
  setup.py install    will install the package

Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message
  --no-user-cfg   ignore pydistutils.cfg in your home directory

Options for 'build_ext' command:
  --cmake-path          Name/path of the CMake executable to use, overriding
                        default auto-detection.
  --build-lib (-b)      directory for compiled extension modules
  --inplace (-i)        ignore build-lib and put compiled extensions into the
                        source directory alongside your pure Python modules
  --force (-f)          forcibly build everything (delete existing
                        CMakeCache.txt)
  --cache (-C)          Pre-load a CMake script to populate the cache.
  --define (-D)         Create or update a CMake CACHE entry (separated by
                        ';')
  --undef (-U)          Remove matching entries from CMake CACHE.
  --generator (-G)      Specify a build system generator.
  --toolset (-T)        Toolset specification for the generator, if supported.
  --platform (-A)       Specify platform name if supported by generator.
  --Wno-dev             Suppress developer warnings.
  --Wdev                Enable developer warnings.
  --Werror              Make specified warnings into errors: dev or
                        deprecated.
  --Wno-error           Make specified warnings not errors.
  --Wdeprecated         Enable deprecated functionality warnings.
  --Wno-deprecated      Suppress deprecated functionality warnings.
  --log-level           Set the log level to one of: ERROR, WARNING, NOTICE,
                        STATUS, VERBOSE, DEBUG, TRACE
  --log-context         Enable the message() command outputting context
                        attached to each message.
  --debug-trycompile    Do not delete the try_compile() build tree. Only
                        useful on one try_compile() at a time.
  --debug-output        Put cmake in a debug mode.
  --debug-find          Put cmake find commands in a debug mode.
  --trace               Put cmake in trace mode.
  --trace-expand        Put cmake in trace mode with variables expanded.
  --trace-format        Put cmake in trace mode and sets the trace output
                        format.
  --trace-source        Put cmake in trace mode, but output only lines of a
                        specified file.
  --trace-redirect      Put cmake in trace mode and redirect trace output to a
                        file instead of stderr.
  --warn-uninitialized  Specify a build system generator.
  --warn-unused-vars    Warn about unused variables.
  --no-warn-unused-cli  Don’t warn about command line options.
  --check-system-vars   Find problems with variable usage in system files.
  --parallel (-j)       The maximum number of concurrent processes to use when
                        building.
  --config              For multi-configuration tools, choose this
                        configuration.
  --clean-first         Build target clean first, then build.
  --verbose (-v)        Enable verbose output - if supported - including the
                        build commands to be executed.
  --strip               Strip before installing.
  --help-generator      list available compilers

usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cmaketools-0.1.3.tar.gz (28.5 kB view details)

Uploaded Source

Built Distribution

cmaketools-0.1.3-py3-none-any.whl (24.5 kB view details)

Uploaded Python 3

File details

Details for the file cmaketools-0.1.3.tar.gz.

File metadata

  • Download URL: cmaketools-0.1.3.tar.gz
  • Upload date:
  • Size: 28.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/2.0.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/47.1.1.post20200604 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.7.6

File hashes

Hashes for cmaketools-0.1.3.tar.gz
Algorithm Hash digest
SHA256 d7fd76c5334b0e01b168c25f6f544ff472aed39ff412cae242e13f0fc88d2602
MD5 4ca4aefd1ca0e8fcb5d77a0467361eaa
BLAKE2b-256 b8eb9639d986648ca38d8c766226eae841ecef25bfd4bed3ff9b037144f8bd73

See more details on using hashes here.

File details

Details for the file cmaketools-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: cmaketools-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 24.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/2.0.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/47.1.1.post20200604 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.7.6

File hashes

Hashes for cmaketools-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 b4a3c73ea0ac0455b19cf0d33542ea6795e735a52c63b75ce574fc7bab6157b1
MD5 f6439a31323f8d51db9c0678ebfd0a31
BLAKE2b-256 567b0ce7cb26eed87677b9f223894dc90916830bca5215ce09173bf068b5aa4a

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page