从 cmake 创建一个 python 包 (deb/rpm)

Creating a python package (deb/rpm) from cmake

我正在尝试从 cmake 创建一个 python 包(deb 和 rpm),最好使用 cpack。我读过

我的共享库的安装工作正常(使用组件安装)。但是我无法理解安装 python 绑定(胶水)代码的文档。使用标准的 cmake 安装机制,我尝试了:

install(
  FILES __init__.py library.py
  DESTINATION ${ACME_PYTHON_PACKAGE_DIR}/project_name
  COMPONENT python)

然后使用蛮力方法结束:

# debian based package (relative path)
set(ACME_PYTHON_PACKAGE_DIR lib/python3/dist-packages)

# rpm based package (full path required)
set(ACME_PYTHON_PACKAGE_DIR /var/lang/lib/python3.8/site-packages)

以上来源于:

debian % python -c 'import site; print(site.getsitepackages())'
['/usr/local/lib/python3.9/dist-packages', '/usr/lib/python3/dist-packages', '/usr/lib/python3.9/dist-packages']

同时:

rpm % python -c 'import site; print(site.getsitepackages())'
['/var/lang/lib/python3.8/site-packages']

很明显,蛮力方法不可移植,并且注定会在 python 的下一个版本中失败。我能想到的唯一可能的解决方案是生成一个临时 setup.py python 脚本(使用 setuptools),它将执行安装。通常 cmake 会调用 following process:

% python setup.py install --root ${ACME_PYTHON_INSTALL_ROOT}

我的问题是:


我确实阅读了一些替代解决方案,例如:

这看起来过于复杂,并且面向仅基于 Debian 的系统。在我的情况下,我需要处理 RPM。

我将post我目前使用的临时解决方案,直到有人提供更强大的东西。

所以我最终偶然发现:

重新使用上面的方法来执行 install 步骤而不是 build 步骤可以按如下方式完成:

find_package(Python COMPONENTS Interpreter)

set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
set(SETUP_DEPS "${CMAKE_CURRENT_SOURCE_DIR}/project_name/__init__.py")
set(SETUP_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build-python")

configure_file(${SETUP_PY_IN} ${SETUP_PY})

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/setup_timestamp
  COMMAND ${Python_EXECUTABLE} ARGS ${SETUP_PY} install --root ${SETUP_OUTPUT}
  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/setup_timestamp
  DEPENDS ${SETUP_DEPS})

add_custom_target(target ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup_timestamp)

然后丑陋的部分是:

install(
  # trailing slash is important:
  DIRECTORY ${SETUP_OUTPUT}/
  DESTINATION "/" # FIXME may cause issues with other cpack generators
  COMPONENT python)

事实证明 install() 的文档对 absolute paths:

非常清楚
DESTINATION
[...]
As absolute paths are not supported by cpack installer generators,
it is preferable to use relative paths throughout.

供参考,这是我的 setup.py.in:

from setuptools import setup

if __name__ == '__main__':
    setup(name='project_name_python',
          version='${PROJECT_VERSION}',
          package_dir={'': '${CMAKE_CURRENT_SOURCE_DIR}'},
          packages=['project_name'])

您可以使用 -B 标志删除 __pycache__ 文件夹:

COMMAND ${Python_EXECUTABLE} ARGS -B ${SETUP_PY} install --root ${SETUP_OUTPUT}

您可以更喜欢并添加 debian 选项,例如:

if(CPACK_BINARY_DEB)
  set(EXTRA_ARG "--install-layout" "deb")
endif()

用作:

COMMAND ${Python_EXECUTABLE} ARGS -B ${SETUP_PY} install --root ${SETUP_OUTPUT} ${EXTRA_ARG}

中所述,丑陋的部分是在 cmake install() 命令中处理绝对路径。我能够重构代码以避免在 install() 中使用绝对路径。我干脆把安装改成了:

install(
  # trailing slash is important:
  DIRECTORY ${SETUP_OUTPUT}/
  # "." syntax is a reliable mechanism, see:
  # https://gitlab.kitware.com/cmake/cmake/-/issues/22616
  DESTINATION "."
  COMPONENT python)

然后只需要:

set(CMAKE_INSTALL_PREFIX "/")
set(CPACK_PACKAGING_INSTALL_PREFIX "/")
include(CPack)

此时所有安装路径现在都需要明确包含 /usr,因为我们已经清除了 CMAKE_INSTALL_PREFIX 的值。

上面已经针对 deb 和 rpm 包进行了测试。 CPACK_BINARY_TGZ 使用上述解决方案 运行 正确: