Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ament_cmake_python/ament_cmake_python-extras.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ print(os.path.relpath(sysconfig.get_path('purelib', **kwargs), start='${CMAKE_IN
endif()
endmacro()

macro(_ament_cmake_python_register_extension_hook)
if(NOT DEFINED AMENT_CMAKE_PYTHON_EXTENSION_REGISTERED)
set(AMENT_CMAKE_PYTHON_EXTENSION_REGISTERED TRUE)

ament_register_extension(
"ament_package"
"ament_cmake_python"
"ament_python_install_registered_packages.cmake")
endif()
endmacro()

include("${ament_cmake_python_DIR}/ament_python_install_module.cmake")
include("${ament_cmake_python_DIR}/ament_python_install_package.cmake")
include("${ament_cmake_python_DIR}/ament_get_python_install_dir.cmake")
145 changes: 26 additions & 119 deletions ament_cmake_python/cmake/ament_python_install_package.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
# :type SKIP_COMPILE: option
#
macro(ament_python_install_package)
_ament_cmake_python_register_extension_hook()
_ament_cmake_python_register_environment_hook()
_ament_cmake_python_install_package(${ARGN})
endmacro()
Expand Down Expand Up @@ -83,129 +84,35 @@ function(_ament_cmake_python_install_package package_name)
set(ARG_DESTINATION ${PYTHON_INSTALL_DIR})
endif()

set(build_dir "${CMAKE_CURRENT_BINARY_DIR}/ament_cmake_python/${package_name}")

string(CONFIGURE "\
from setuptools import find_packages
from setuptools import setup
setup(
name='${package_name}',
version='${ARG_VERSION}',
packages=find_packages(
include=('${package_name}', '${package_name}.*')),
)
" setup_py_content)

file(GENERATE
OUTPUT "${build_dir}/setup.py"
CONTENT "${setup_py_content}"
)

if(AMENT_CMAKE_SYMLINK_INSTALL)
add_custom_target(
ament_cmake_python_symlink_${package_name}
COMMAND ${CMAKE_COMMAND} -E create_symlink
"${ARG_PACKAGE_DIR}" "${build_dir}/${package_name}"
)
set(egg_dependencies ament_cmake_python_symlink_${package_name})

if(ARG_SETUP_CFG)
add_custom_target(
ament_cmake_python_symlink_${package_name}_setup
COMMAND ${CMAKE_COMMAND} -E create_symlink
"${ARG_SETUP_CFG}" "${build_dir}/setup.cfg"
)
list(APPEND egg_dependencies ament_cmake_python_symlink_${package_name}_setup)
endif()
get_property(_pkgs GLOBAL PROPERTY AMENT_CMAKE_PYTHON_PKGS)
list(FIND _pkgs "${package_name}" _idx)
if(_idx EQUAL -1)
set_property(GLOBAL APPEND PROPERTY AMENT_CMAKE_PYTHON_PKGS "${package_name}")
else()
add_custom_target(
ament_cmake_python_copy_${package_name}
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${ARG_PACKAGE_DIR}" "${build_dir}/${package_name}"
)
set(egg_dependencies ament_cmake_python_copy_${package_name})

if(ARG_SETUP_CFG)
add_custom_target(
ament_cmake_python_copy_${package_name}_setup
COMMAND ${CMAKE_COMMAND} -E copy
"${ARG_SETUP_CFG}" "${build_dir}/setup.cfg"
)
list(APPEND egg_dependencies ament_cmake_python_copy_${package_name}_setup)
endif()
message(STATUS "ament_python_install_package: extending '${package_name}'")
endif()

# Technically, we should call find_package(Python3) first to ensure that Python3::Interpreter
# is available. But we skip this here because this macro requires ament_cmake, and ament_cmake
# calls find_package(Python3) for us.
get_executable_path(python_interpreter Python3::Interpreter BUILD)

add_custom_target(
ament_cmake_python_build_${package_name}_egg ALL
COMMAND ${python_interpreter} setup.py egg_info
WORKING_DIRECTORY "${build_dir}"
DEPENDS ${egg_dependencies}
)

set(python_version "py${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}")

set(egg_name "${package_name}")
set(egg_install_name "${egg_name}-${ARG_VERSION}")
set(egg_install_name "${egg_install_name}-${python_version}")

install(
DIRECTORY "${build_dir}/${egg_name}.egg-info/"
DESTINATION "${ARG_DESTINATION}/${egg_install_name}.egg-info"
)

if(ARG_SCRIPTS_DESTINATION)
file(MAKE_DIRECTORY "${build_dir}/scripts") # setup.py may or may not create it

add_custom_target(
ament_cmake_python_build_${package_name}_scripts ALL
COMMAND ${python_interpreter} setup.py install_scripts -d scripts
WORKING_DIRECTORY "${build_dir}"
DEPENDS ${egg_dependencies}
)

if(NOT AMENT_CMAKE_SYMLINK_INSTALL)
# Not needed for nor supported by symlink installs
set(_extra_install_args USE_SOURCE_PERMISSIONS)
endif()

install(
DIRECTORY "${build_dir}/scripts/"
DESTINATION "${ARG_SCRIPTS_DESTINATION}/"
${_extra_install_args}
)
get_property(_dirs GLOBAL PROPERTY AMENT_CMAKE_PYTHON_${package_name}_PACKAGE_DIRS)
list(FIND _dirs "${ARG_PACKAGE_DIR}" _didx)
if(_didx EQUAL -1)
set_property(GLOBAL APPEND PROPERTY AMENT_CMAKE_PYTHON_${package_name}_PACKAGE_DIRS "${ARG_PACKAGE_DIR}")
else()
message(WARNING "duplicate PACKAGE_DIR for '${package_name}', skipping")
endif()

install(
DIRECTORY "${ARG_PACKAGE_DIR}/"
DESTINATION "${ARG_DESTINATION}/${package_name}"
PATTERN "*.pyc" EXCLUDE
PATTERN "__pycache__" EXCLUDE
)

if(NOT ARG_SKIP_COMPILE)
get_executable_path(python_interpreter_config Python3::Interpreter CONFIGURE)
# compile Python files
install(CODE
"execute_process(
COMMAND
\"${python_interpreter_config}\" \"-m\" \"compileall\"
\"${CMAKE_INSTALL_PREFIX}/${ARG_DESTINATION}/${package_name}\"
)"
)
endif()
_ament_cmake_python_override(SKIP_COMPILE)
_ament_cmake_python_override(VERSION)
_ament_cmake_python_override(SETUP_CFG)
_ament_cmake_python_override(DESTINATION)
_ament_cmake_python_override(SCRIPTS_DESTINATION)
endfunction()

if(package_name IN_LIST AMENT_CMAKE_PYTHON_INSTALL_INSTALLED_NAMES)
message(FATAL_ERROR
"ament_python_install_package() a Python module file or package with "
"the same name '${package_name}' has been installed before")
macro(_ament_cmake_python_override param)
set(_prop "AMENT_CMAKE_PYTHON_${package_name}_${param}")
set(_val "${ARG_${param}}")
get_property(_old_val GLOBAL PROPERTY ${_prop})
if(_old_val AND NOT _old_val STREQUAL "${_val}")
message(WARNING "${param} for '${package_name}' changed from '${_old_val}' to '${_val}'")
endif()
list(APPEND AMENT_CMAKE_PYTHON_INSTALL_INSTALLED_NAMES "${package_name}")
set(AMENT_CMAKE_PYTHON_INSTALL_INSTALLED_NAMES
"${AMENT_CMAKE_PYTHON_INSTALL_INSTALLED_NAMES}" PARENT_SCOPE)
endfunction()
set_property(GLOBAL PROPERTY ${_prop} "${_val}")
endmacro()
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function(ament_cmake_python_install_registered_packages)
get_property(_pkgs GLOBAL PROPERTY AMENT_CMAKE_PYTHON_PKGS)
foreach(pkg IN LISTS _pkgs)
_ament_cmake_python_install_package_impl(${pkg})
endforeach()
endfunction()

function(_ament_cmake_python_install_package_impl package_name)
foreach(_prop IN ITEMS SKIP_COMPILE VERSION SETUP_CFG DESTINATION SCRIPTS_DESTINATION PACKAGE_DIRS)
get_property(_${_prop} GLOBAL PROPERTY AMENT_CMAKE_PYTHON_${package_name}_${_prop})
endforeach()

_ament_cmake_python_prepare_build(${package_name})
_ament_cmake_python_copy_build_files(${package_name})

# Technically, we should call find_package(Python3) first to ensure that Python3::Interpreter
# is available. But we skip this here because this macro requires ament_cmake, and ament_cmake
# calls find_package(Python3) for us.
get_executable_path(python_interpreter Python3::Interpreter BUILD)

_ament_cmake_python_generate_egg(${package_name})

if(_SCRIPTS_DESTINATION)
_ament_cmake_python_install_scripts(${package_name})
endif()

_ament_cmake_python_install_sources(${package_name})

if(NOT _SKIP_COMPILE)
_ament_cmake_python_byte_compile(${package_name})
endif()

endfunction()

macro(_ament_cmake_python_prepare_build package_name)
set(_build_dir "${CMAKE_CURRENT_BINARY_DIR}/ament_cmake_python/${package_name}")

string(CONFIGURE "\
from setuptools import find_packages
from setuptools import setup

setup(
name='${package_name}',
version='${_VERSION}',
packages=find_packages(
include=('${package_name}', '${package_name}.*')),
)
" setup_py_content)

file(GENERATE
OUTPUT "${_build_dir}/setup.py"
CONTENT "${setup_py_content}"
)

endmacro()

macro(_ament_cmake_python_copy_build_files package_name)
set(_sync_target "ament_cmake_python_sync_${package_name}")

add_custom_target(${_sync_target} DEPENDS ${_PACKAGE_DIRS} ${_SETUP_CFG})

foreach(_dir IN LISTS _PACKAGE_DIRS)
add_custom_command(TARGET ${_sync_target}
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${_dir}" "${_build_dir}/${package_name}"
)
endforeach()

if(_SETUP_CFG)
add_custom_command(TARGET ${_sync_target}
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${_SETUP_CFG}" "${_build_dir}/setup.cfg"
)
endif()

endmacro()

macro(_ament_cmake_python_generate_egg package_name)
add_custom_target(
ament_cmake_python_build_${package_name}_egg ALL
COMMAND ${python_interpreter} setup.py egg_info
WORKING_DIRECTORY "${_build_dir}"
DEPENDS ${_sync_target}
)

set(python_version "py${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}")

set(egg_name "${package_name}")
set(egg_install_name "${egg_name}-${_VERSION}")
set(egg_install_name "${egg_install_name}-${python_version}")

install(
DIRECTORY "${_build_dir}/${egg_name}.egg-info/"
DESTINATION "${_DESTINATION}/${egg_install_name}.egg-info"
)
endmacro()

macro(_ament_cmake_python_install_scripts package_name)
file(MAKE_DIRECTORY "${_build_dir}/scripts") # setup.py may or may not create it

add_custom_target(
ament_cmake_python_build_${package_name}_scripts ALL
COMMAND ${python_interpreter} setup.py install_scripts -d scripts
WORKING_DIRECTORY "${_build_dir}"
DEPENDS ${_sync_target}
)

if(NOT AMENT_CMAKE_SYMLINK_INSTALL)
# Not needed for nor supported by symlink installs
set(_extra_install_args USE_SOURCE_PERMISSIONS)
endif()

install(
DIRECTORY "${_build_dir}/scripts/"
DESTINATION "${_SCRIPTS_DESTINATION}/"
${_extra_install_args}
)
endmacro()

macro(_ament_cmake_python_install_sources package_name)
set(_DIRS_TO_INSTALL "${_PACKAGE_DIRS}")
list(TRANSFORM _DIRS_TO_INSTALL APPEND "/")
if(AMENT_CMAKE_SYMLINK_INSTALL)
install(
DIRECTORY ${_DIRS_TO_INSTALL}
DESTINATION "${_DESTINATION}/${package_name}"
PATTERN "*.pyc" EXCLUDE
PATTERN "__pycache__" EXCLUDE
)
else()
# cmake does not support overwriting a more recently modified file
# we must remove any existing files before installing the new package
install(CODE
"set(_dest \"\${CMAKE_INSTALL_PREFIX}/${_DESTINATION}/${package_name}\")
set(_dirs ${_DIRS_TO_INSTALL})
foreach(_dir IN LISTS _dirs)
file(GLOB_RECURSE _rel_files RELATIVE \"\${_dir}\" \"\${_dir}/*\")
foreach(_file IN LISTS _rel_files)
file(REMOVE \"\${_dest}/\${_file}\")
endforeach()
file(INSTALL
DESTINATION \"\${_dest}\"
TYPE DIRECTORY
FILES \"\${_dir}\"
PATTERN \"*.pyc\" EXCLUDE
PATTERN \"__pycache__\" EXCLUDE
)
endforeach()"
)
endif()
endmacro()

macro(_ament_cmake_python_byte_compile package_name)
get_executable_path(python_interpreter_config Python3::Interpreter CONFIGURE)
# compile Python files
install(CODE
"execute_process(
COMMAND
\"${python_interpreter_config}\" \"-m\" \"compileall\"
\"${CMAKE_INSTALL_PREFIX}/${_DESTINATION}/${package_name}\"
)"
)
endmacro()

ament_cmake_python_install_registered_packages()
1 change: 0 additions & 1 deletion ament_cmake_python/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
<author email="[email protected]">Michel Hidalgo</author>

<buildtool_depend>ament_cmake_core</buildtool_depend>

<buildtool_export_depend>ament_cmake_core</buildtool_export_depend>

<export>
Expand Down
Loading