Skip to content
Closed
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

build/
install/
log/
docker
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,157 @@
# 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)
foreach(_dir IN LISTS _PACKAGE_DIRS)
install(
DIRECTORY "${_dir}/"
DESTINATION "${_DESTINATION}/${package_name}"
PATTERN "*.pyc" EXCLUDE
PATTERN "__pycache__" EXCLUDE
)
endforeach()
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()
3 changes: 3 additions & 0 deletions ament_cmake_python_test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
31 changes: 31 additions & 0 deletions ament_cmake_python_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.12)

project(ament_cmake_python_test NONE)

find_package(ament_cmake_core REQUIRED)

if(BUILD_TESTING)
find_package(ament_cmake_pytest REQUIRED)
find_package(ament_cmake_python REQUIRED)

ament_python_install_package(
ament_python_test_package
PACKAGE_DIR test/ament_python_test_package
)
ament_python_install_package(
ament_python_test_package_overlay
PACKAGE_DIR test/ament_python_test_package
)
ament_python_install_package(
ament_python_test_package_overlay
PACKAGE_DIR test/ament_python_test_package_overlay
)

ament_add_pytest_test(
test_ament_python_install_package
test/test_ament_python_install_package.py
ENV TEST_PACKAGE_INSTALL_DIR=${CMAKE_INSTALL_PREFIX}/${PYTHON_INSTALL_DIR}
)
endif()

ament_package()
Loading