From ad1a848edb75822e422146cd870970b83b182fa7 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 21:48:40 -0700 Subject: [PATCH 01/52] Add unified Python wheel configuration Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- cmake/roboplan_unified_repair/CMakeLists.txt | 37 ++++++++++++++++++ pyproject.toml | 41 ++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 cmake/roboplan_unified_repair/CMakeLists.txt create mode 100644 pyproject.toml diff --git a/cmake/roboplan_unified_repair/CMakeLists.txt b/cmake/roboplan_unified_repair/CMakeLists.txt new file mode 100644 index 00000000..9d4cf06a --- /dev/null +++ b/cmake/roboplan_unified_repair/CMakeLists.txt @@ -0,0 +1,37 @@ +install(CODE " + function(_roboplan_patch_rpath file rpath) + set(_patch_file \"\${file}\") + if(IS_SYMLINK \"\${_patch_file}\") + file(READ_SYMLINK \"\${_patch_file}\" _symlink_target) + if(IS_ABSOLUTE \"\${_symlink_target}\") + return() + endif() + get_filename_component(_symlink_dir \"\${_patch_file}\" DIRECTORY) + set(_patch_file \"\${_symlink_dir}/\${_symlink_target}\") + endif() + + if(EXISTS \"\${_patch_file}\") + execute_process( + COMMAND chmod u+w \"\${_patch_file}\" + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND \"${ROBOPLAN_UNIFIED_PATCHELF}\" --set-rpath \"\${rpath}\" \"\${_patch_file}\" + COMMAND_ERROR_IS_FATAL ANY + ) + endif() + endfunction() + + file(GLOB_RECURSE _roboplan_extensions \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/roboplan/*.so\") + foreach(_file IN LISTS _roboplan_extensions) + _roboplan_patch_rpath(\"\${_file}\" \"\\\$ORIGIN/../../lib:\\\$ORIGIN/../../cmeel.prefix/lib\") + endforeach() + + file(GLOB _roboplan_libraries + \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/lib/libfilters.so*\" + \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/lib/libroboplan*.so*\" + ) + foreach(_file IN LISTS _roboplan_libraries) + _roboplan_patch_rpath(\"\${_file}\" \"\\\$ORIGIN:\\\$ORIGIN/../cmeel.prefix/lib\") + endforeach() +") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0941aed8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = [ + "scikit-build-core >=0.10", + "nanobind >=1.3.2", + "patchelf; platform_system == 'Linux'", +] +build-backend = "scikit_build_core.build" + +[project] +name = "roboplan" +version = "0.4.0" +description = "Modern robot motion planning library based on Pinocchio." +readme = "README.md" +requires-python = ">=3.10" +authors = [ + { name = "Sebastian Castro", email = "sebas.a.castro@gmail.com" }, + { name = "JafarAbdi", email = "jafar.uruc@gmail.com" }, +] +classifiers = [ + "License :: OSI Approved :: MIT License", +] +dependencies = [ + "matplotlib", + "numpy", + "pin", +] + +[project.urls] +Homepage = "https://github.com/sea-bass/roboplan" + +[tool.scikit-build] +minimum-version = "build-system.requires" +build-dir = "build/{wheel_tag}" +cmake.define.BUILD_PYTHON_BINDINGS = "ON" +cmake.define.BUILD_TESTING = "OFF" +cmake.define.GENERATE_PYTHON_STUBS = "OFF" +cmake.define.CMAKE_INSTALL_RPATH = "$ORIGIN/../../lib" +cmake.define.CMAKE_INSTALL_RPATH_USE_LINK_PATH = "FALSE" + +[tool.scikit-build.sdist] +exclude = [".omo/**"] From 5b0e8a681c5f016ce8d638c2aecae905887923e6 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 21:48:57 -0700 Subject: [PATCH 02/52] Add cmeel split package manifests Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/cmeel/libroboplan/pyproject.toml | 41 ++++++++++++++++++++ packaging/cmeel/roboplan/pyproject.toml | 45 ++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 packaging/cmeel/libroboplan/pyproject.toml create mode 100644 packaging/cmeel/roboplan/pyproject.toml diff --git a/packaging/cmeel/libroboplan/pyproject.toml b/packaging/cmeel/libroboplan/pyproject.toml new file mode 100644 index 00000000..aede67f7 --- /dev/null +++ b/packaging/cmeel/libroboplan/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +build-backend = "cmeel" +requires = [ + "cmeel-yaml-cpp[build]", + "cmeel[build]", + "libpinocchio[build] == 4.0.0", + "patchelf", +] + +[project] +name = "libroboplan" +version = "0.4.0" +description = "Native C++ libraries, headers, and CMake packages for RoboPlan." +readme = "../../../README.md" +license = "MIT" +requires-python = ">=3.10" +authors = [ + { name = "Sebastian Castro", email = "sebas.a.castro@gmail.com" }, + { name = "JafarAbdi", email = "jafar.uruc@gmail.com" }, +] +classifiers = [ + "License :: OSI Approved :: MIT License", +] +dependencies = [ + "cmeel-yaml-cpp", + "libpinocchio == 4.0.0", +] + +[project.urls] +Homepage = "https://github.com/sea-bass/roboplan" + +[tool.cmeel] +source = "../../.." +has-sitelib = false +run-tests = false +configure-args = [ + "-DROBOPLAN_CMEEL=ON", + "-DBUILD_PYTHON_BINDINGS=OFF", + "-DBUILD_TESTING=OFF", + "-DBUILD_TESTING_OINK=OFF", +] diff --git a/packaging/cmeel/roboplan/pyproject.toml b/packaging/cmeel/roboplan/pyproject.toml new file mode 100644 index 00000000..7354a446 --- /dev/null +++ b/packaging/cmeel/roboplan/pyproject.toml @@ -0,0 +1,45 @@ +[build-system] +build-backend = "cmeel" +requires = [ + "cmeel[build]", + "libroboplan == 0.4.0", + "nanobind >=1.3.2", + "numpy", + "patchelf", +] + +[project] +name = "roboplan" +version = "0.4.0" +description = "Python bindings for RoboPlan." +readme = "../../../README.md" +license = "MIT" +requires-python = ">=3.10" +authors = [ + { name = "Sebastian Castro", email = "sebas.a.castro@gmail.com" }, + { name = "JafarAbdi", email = "jafar.uruc@gmail.com" }, +] +classifiers = [ + "License :: OSI Approved :: MIT License", +] +dependencies = [ + "libroboplan == 0.4.0", + "matplotlib", + "numpy", + "pin == 4.0.0", +] + +[project.urls] +Homepage = "https://github.com/sea-bass/roboplan" + +[tool.cmeel] +source = "../../.." +run-tests = false +configure-args = [ + "-DROBOPLAN_CMEEL=ON", + "-DBUILD_STANDALONE_PYTHON_BINDINGS=ON", + "-DBUILD_PYTHON_BINDINGS=ON", + "-DBUILD_TESTING=OFF", + "-DBUILD_TESTING_OINK=OFF", + "-DGENERATE_PYTHON_STUBS=OFF", +] From 04616d3ac277aad8208e709e549b92259e2f3462 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 21:49:16 -0700 Subject: [PATCH 03/52] Add root CMake packaging modes Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- CMakeLists.txt | 176 +++++++++++++++++++++ cmake/roboplan_cmeel_repair/CMakeLists.txt | 51 ++++++ 2 files changed, 227 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/roboplan_cmeel_repair/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..d2c236f9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,176 @@ +cmake_minimum_required(VERSION 3.22) +project(roboplan VERSION 0.4.0) + +option(ROBOPLAN_CMEEL "Build RoboPlan with cmeel packaging conventions" OFF) +option( + BUILD_STANDALONE_PYTHON_BINDINGS + "Build only Python bindings against pre-installed RoboPlan native packages" + OFF +) + +if(BUILD_STANDALONE_PYTHON_BINDINGS) + find_package(roboplan_example_models REQUIRED CONFIG) + find_package(roboplan REQUIRED CONFIG) + find_package(roboplan_simple_ik REQUIRED CONFIG) + find_package(roboplan_oink REQUIRED CONFIG) + find_package(roboplan_rrt REQUIRED CONFIG) + find_package(roboplan_toppra REQUIRED CONFIG) + + if(TARGET roboplan_example_models::roboplan_example_models AND NOT TARGET roboplan_example_models) + add_library(roboplan_example_models ALIAS roboplan_example_models::roboplan_example_models) + endif() + if(TARGET roboplan::roboplan AND NOT TARGET roboplan) + add_library(roboplan ALIAS roboplan::roboplan) + endif() + if(TARGET roboplan::filters AND NOT TARGET filters) + add_library(filters ALIAS roboplan::filters) + endif() + if(TARGET roboplan_simple_ik::roboplan_simple_ik AND NOT TARGET roboplan_simple_ik) + add_library(roboplan_simple_ik ALIAS roboplan_simple_ik::roboplan_simple_ik) + endif() + if(TARGET roboplan_oink::roboplan_oink AND NOT TARGET roboplan_oink) + add_library(roboplan_oink ALIAS roboplan_oink::roboplan_oink) + endif() + if(TARGET roboplan_rrt::roboplan_rrt AND NOT TARGET roboplan_rrt) + add_library(roboplan_rrt ALIAS roboplan_rrt::roboplan_rrt) + endif() + if(TARGET roboplan_toppra::roboplan_toppra AND NOT TARGET roboplan_toppra) + add_library(roboplan_toppra ALIAS roboplan_toppra::roboplan_toppra) + endif() + + add_subdirectory(roboplan_example_models/bindings) + add_subdirectory(roboplan/bindings) + add_subdirectory(roboplan_simple_ik/bindings) + add_subdirectory(roboplan_oink/bindings) + add_subdirectory(roboplan_rrt/bindings) + add_subdirectory(roboplan_toppra/bindings) +else() + add_subdirectory(roboplan_example_models) + add_subdirectory(roboplan) + add_subdirectory(roboplan_simple_ik) + add_subdirectory(roboplan_oink) + add_subdirectory(roboplan_rrt) + add_subdirectory(roboplan_toppra) +endif() + +if(SKBUILD) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_program(ROBOPLAN_UNIFIED_PATCHELF patchelf REQUIRED) + endif() + + foreach(target IN ITEMS + roboplan_example_models + roboplan + filters + roboplan_simple_ik + roboplan_oink + roboplan_rrt + roboplan_toppra) + if(TARGET ${target}) + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN") + endif() + endforeach() + + foreach(target IN ITEMS + _example_models_ext + _core_ext + _filters_ext + _simple_ik_ext + _optimal_ik_ext + _rrt_ext + _toppra_ext) + if(TARGET ${target}) + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN/../../lib") + endif() + endforeach() + + foreach(library IN ITEMS + OsqpEigen + boost_filesystem + boost_serialization + boost_system + coal + console_bridge + gz-math + gz-utils + octomap + octomath + osqp + pinocchio_collision + pinocchio_default + pinocchio_extra + pinocchio_parsers + qdldl + qhull_r + sdformat + tinyxml2 + toppra + urdfdom_model + urdfdom_sensor + urdfdom_world + yaml-cpp) + find_library(ROBOPLAN_UNIFIED_${library}_LIBRARY NAMES ${library}) + if(ROBOPLAN_UNIFIED_${library}_LIBRARY) + install(FILES "${ROBOPLAN_UNIFIED_${library}_LIBRARY}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_UNIFIED_${library}_LIBRARY}" ROBOPLAN_UNIFIED_${library}_REAL_LIBRARY) + install(FILES "${ROBOPLAN_UNIFIED_${library}_REAL_LIBRARY}" DESTINATION lib) + endif() + endforeach() + + foreach(library IN ITEMS + libassimp.so.6 + libboost_filesystem.so.1.88.0 + libboost_serialization.so.1.88.0 + libboost_system.so.1.88.0 + libgz-math.so.9 + libgz-utils.so.4 + liboctomap.so.1.10 + liboctomath.so.1.10 + libqhull_r.so.8.0 + libsdformat.so.16 + libtinyxml2.so.11 + libyaml-cpp.so.0.8) + find_file(ROBOPLAN_UNIFIED_${library}_FILE NAMES ${library} + PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" + NO_DEFAULT_PATH) + if(ROBOPLAN_UNIFIED_${library}_FILE) + install(FILES "${ROBOPLAN_UNIFIED_${library}_FILE}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_UNIFIED_${library}_FILE}" ROBOPLAN_UNIFIED_${library}_REAL_FILE) + install(FILES "${ROBOPLAN_UNIFIED_${library}_REAL_FILE}" DESTINATION lib) + endif() + endforeach() + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_subdirectory(cmake/roboplan_unified_repair) + endif() +endif() + +if(ROBOPLAN_CMEEL AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_program(ROBOPLAN_CMEEL_PATCHELF patchelf REQUIRED) + foreach(library IN ITEMS + OsqpEigen + osqp + qdldl + toppra) + find_library(ROBOPLAN_CMEEL_${library}_LIBRARY NAMES ${library}) + if(ROBOPLAN_CMEEL_${library}_LIBRARY) + install(FILES "${ROBOPLAN_CMEEL_${library}_LIBRARY}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_LIBRARY}" ROBOPLAN_CMEEL_${library}_REAL_LIBRARY) + install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_LIBRARY}" DESTINATION lib) + endif() + endforeach() + + foreach(library IN ITEMS + libOsqpEigen.so.0.11.0) + find_file(ROBOPLAN_CMEEL_${library}_FILE NAMES ${library} + PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" + NO_DEFAULT_PATH) + if(ROBOPLAN_CMEEL_${library}_FILE) + install(FILES "${ROBOPLAN_CMEEL_${library}_FILE}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_FILE}" ROBOPLAN_CMEEL_${library}_REAL_FILE) + install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_FILE}" DESTINATION lib) + endif() + endforeach() + + add_subdirectory(cmake/roboplan_cmeel_repair) +endif() diff --git a/cmake/roboplan_cmeel_repair/CMakeLists.txt b/cmake/roboplan_cmeel_repair/CMakeLists.txt new file mode 100644 index 00000000..a26317cc --- /dev/null +++ b/cmake/roboplan_cmeel_repair/CMakeLists.txt @@ -0,0 +1,51 @@ +install(CODE " + function(_roboplan_cmeel_patch_rpath file rpath) + set(_patch_file \"\${file}\") + if(IS_SYMLINK \"\${_patch_file}\") + file(READ_SYMLINK \"\${_patch_file}\" _symlink_target) + if(IS_ABSOLUTE \"\${_symlink_target}\") + return() + endif() + get_filename_component(_symlink_dir \"\${_patch_file}\" DIRECTORY) + set(_patch_file \"\${_symlink_dir}/\${_symlink_target}\") + endif() + + if(EXISTS \"\${_patch_file}\") + execute_process( + COMMAND chmod u+w \"\${_patch_file}\" + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND \"${ROBOPLAN_CMEEL_PATCHELF}\" --set-rpath \"\${rpath}\" \"\${_patch_file}\" + COMMAND_ERROR_IS_FATAL ANY + ) + endif() + endfunction() + + file(GLOB_RECURSE _roboplan_cmeel_installed_shared_objects + \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/*.so\" + \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/*.so.*\" + ) + + set(_roboplan_cmeel_extensions) + set(_roboplan_cmeel_libraries) + foreach(_file IN LISTS _roboplan_cmeel_installed_shared_objects) + if(_file MATCHES \"/site-packages/roboplan/.+\\\\.so$\") + list(APPEND _roboplan_cmeel_extensions \"\${_file}\") + elseif(_file MATCHES \"/lib/lib(filters|roboplan.*|OsqpEigen|osqp|qdldl|toppra)\\\\.so(\\\\..*)?$\") + list(APPEND _roboplan_cmeel_libraries \"\${_file}\") + endif() + endforeach() + + if(BUILD_STANDALONE_PYTHON_BINDINGS AND NOT _roboplan_cmeel_extensions) + message(FATAL_ERROR \"ROBOPLAN_CMEEL RPATH repair found no Python extensions\") + endif() + + foreach(_file IN LISTS _roboplan_cmeel_extensions) + _roboplan_cmeel_patch_rpath(\"\${_file}\" \"\\\$ORIGIN/../../../..\") + endforeach() + + foreach(_file IN LISTS _roboplan_cmeel_libraries) + _roboplan_cmeel_patch_rpath(\"\${_file}\" \"\\\$ORIGIN\") + endforeach() +") From e4bc40028921e7e720fb0a5ac26561b5c3297fc9 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 21:49:36 -0700 Subject: [PATCH 04/52] Prepare native targets for packaged RPATHs Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan/CMakeLists.txt | 15 +++++++++++++++ roboplan_example_models/CMakeLists.txt | 6 ++++++ roboplan_oink/CMakeLists.txt | 10 +++++++++- roboplan_rrt/CMakeLists.txt | 10 +++++++++- roboplan_simple_ik/CMakeLists.txt | 10 +++++++++- roboplan_toppra/CMakeLists.txt | 10 +++++++++- 6 files changed, 57 insertions(+), 4 deletions(-) diff --git a/roboplan/CMakeLists.txt b/roboplan/CMakeLists.txt index d94aa251..15b38c21 100644 --- a/roboplan/CMakeLists.txt +++ b/roboplan/CMakeLists.txt @@ -52,6 +52,9 @@ add_library(roboplan SHARED src/core/scene_utils.cpp src/core/types.cpp ) +if(NOT TARGET roboplan::roboplan) + add_library(roboplan::roboplan ALIAS roboplan) +endif() target_include_directories(roboplan PUBLIC $ $ @@ -69,6 +72,12 @@ else() target_link_libraries(roboplan PRIVATE yaml-cpp::yaml-cpp) endif() set_property(TARGET roboplan PROPERTY CXX_STANDARD 20) +if(SKBUILD OR ROBOPLAN_CMEEL) + set_target_properties(roboplan PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() add_library(filters SHARED src/filters/se3_low_pass_filter.cpp @@ -83,6 +92,12 @@ target_link_libraries(filters pinocchio::pinocchio ) set_property(TARGET filters PROPERTY CXX_STANDARD 20) +if(SKBUILD OR ROBOPLAN_CMEEL) + set_target_properties(filters PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() list(APPEND TARGETS_LIST filters) set(ROBOPLAN_TARGETS ${TARGETS_LIST}) diff --git a/roboplan_example_models/CMakeLists.txt b/roboplan_example_models/CMakeLists.txt index e1e00780..6167e39f 100644 --- a/roboplan_example_models/CMakeLists.txt +++ b/roboplan_example_models/CMakeLists.txt @@ -25,6 +25,12 @@ target_include_directories(roboplan_example_models PUBLIC $ ) set_property(TARGET roboplan_example_models PROPERTY CXX_STANDARD 20) +if(SKBUILD OR ROBOPLAN_CMEEL) + set_target_properties(roboplan_example_models PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() install(DIRECTORY include/ DESTINATION include) install( diff --git a/roboplan_oink/CMakeLists.txt b/roboplan_oink/CMakeLists.txt index 82eadd60..bc7cfb7a 100644 --- a/roboplan_oink/CMakeLists.txt +++ b/roboplan_oink/CMakeLists.txt @@ -13,7 +13,9 @@ option(BUILD_TESTING_OINK "" ${BUILD_TESTING}) include(FetchContent) # Find dependencies. -find_package(roboplan REQUIRED) +if(NOT TARGET roboplan::roboplan) + find_package(roboplan REQUIRED) +endif() # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -106,6 +108,12 @@ target_link_libraries(roboplan_oink OsqpEigen::OsqpEigen ) set_property(TARGET roboplan_oink PROPERTY CXX_STANDARD 20) +if(SKBUILD OR ROBOPLAN_CMEEL) + set_target_properties(roboplan_oink PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() install( TARGETS diff --git a/roboplan_rrt/CMakeLists.txt b/roboplan_rrt/CMakeLists.txt index c82d2061..9b9ea70c 100644 --- a/roboplan_rrt/CMakeLists.txt +++ b/roboplan_rrt/CMakeLists.txt @@ -6,7 +6,9 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() # Find dependencies. -find_package(roboplan REQUIRED) +if(NOT TARGET roboplan::roboplan) + find_package(roboplan REQUIRED) +endif() # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -24,6 +26,12 @@ target_include_directories(roboplan_rrt PUBLIC ) set_property(TARGET roboplan_rrt PROPERTY CXX_STANDARD 20) target_link_libraries(roboplan_rrt roboplan::roboplan) +if(SKBUILD OR ROBOPLAN_CMEEL) + set_target_properties(roboplan_rrt PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() install( TARGETS diff --git a/roboplan_simple_ik/CMakeLists.txt b/roboplan_simple_ik/CMakeLists.txt index fbd51eee..a90d7d7e 100644 --- a/roboplan_simple_ik/CMakeLists.txt +++ b/roboplan_simple_ik/CMakeLists.txt @@ -6,7 +6,9 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() # Find dependencies. -find_package(roboplan REQUIRED) +if(NOT TARGET roboplan::roboplan) + find_package(roboplan REQUIRED) +endif() # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -26,6 +28,12 @@ target_link_libraries(roboplan_simple_ik roboplan::roboplan ) set_property(TARGET roboplan_simple_ik PROPERTY CXX_STANDARD 20) +if(SKBUILD OR ROBOPLAN_CMEEL) + set_target_properties(roboplan_simple_ik PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() install( TARGETS diff --git a/roboplan_toppra/CMakeLists.txt b/roboplan_toppra/CMakeLists.txt index c63fb1c6..8a5cc116 100644 --- a/roboplan_toppra/CMakeLists.txt +++ b/roboplan_toppra/CMakeLists.txt @@ -6,7 +6,9 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() # Find dependencies. -find_package(roboplan REQUIRED) +if(NOT TARGET roboplan::roboplan) + find_package(roboplan REQUIRED) +endif() # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -50,6 +52,12 @@ target_link_libraries(roboplan_toppra roboplan::roboplan toppra::toppra ) +if(SKBUILD OR ROBOPLAN_CMEEL) + set_target_properties(roboplan_toppra PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() install( TARGETS From 18e75d64cc07fb8cd6696d6cf5913cbe804e4bb5 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 21:49:56 -0700 Subject: [PATCH 05/52] Install core binding modules for package builds Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan/bindings/CMakeLists.txt | 25 +++++++++++++++++++ .../bindings/CMakeLists.txt | 14 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/roboplan/bindings/CMakeLists.txt b/roboplan/bindings/CMakeLists.txt index 647af042..2a542ffc 100644 --- a/roboplan/bindings/CMakeLists.txt +++ b/roboplan/bindings/CMakeLists.txt @@ -60,6 +60,17 @@ set_property(TARGET _core_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_core_ext PRIVATE roboplan ) +if(SKBUILD) + set_target_properties(_core_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../lib" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +elseif(ROBOPLAN_CMEEL) + set_target_properties(_core_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../../.." + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() # PEP 420 namespace package: no roboplan/__init__.py is shipped. Resolve the # Python install destination per build context: @@ -73,6 +84,9 @@ target_link_libraries(_core_ext PRIVATE if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") +elseif(ROBOPLAN_CMEEL) + set(PYTHON_DESTINATION + "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() @@ -96,6 +110,17 @@ set_property(TARGET _filters_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_filters_ext PRIVATE filters ) +if(SKBUILD) + set_target_properties(_filters_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../lib" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +elseif(ROBOPLAN_CMEEL) + set_target_properties(_filters_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../../.." + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() install(TARGETS _core_ext LIBRARY DESTINATION "${PYTHON_DESTINATION}/roboplan/core") install(TARGETS _filters_ext LIBRARY DESTINATION "${PYTHON_DESTINATION}/roboplan/filters") diff --git a/roboplan_example_models/bindings/CMakeLists.txt b/roboplan_example_models/bindings/CMakeLists.txt index a38279cb..89ee2bd7 100644 --- a/roboplan_example_models/bindings/CMakeLists.txt +++ b/roboplan_example_models/bindings/CMakeLists.txt @@ -39,6 +39,17 @@ set_property(TARGET _example_models_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_example_models_ext PRIVATE roboplan_example_models ) +if(SKBUILD) + set_target_properties(_example_models_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../lib" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +elseif(ROBOPLAN_CMEEL) + set_target_properties(_example_models_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../../.." + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -51,6 +62,9 @@ target_link_libraries(_example_models_ext PRIVATE if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") +elseif(ROBOPLAN_CMEEL) + set(PYTHON_DESTINATION + "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() From 80230ed342e499db9b2b0d2a964f457b36adbcc9 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 21:50:13 -0700 Subject: [PATCH 06/52] Install solver binding modules for package builds Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan_oink/bindings/CMakeLists.txt | 14 ++++++++++++++ roboplan_rrt/bindings/CMakeLists.txt | 14 ++++++++++++++ roboplan_simple_ik/bindings/CMakeLists.txt | 14 ++++++++++++++ roboplan_toppra/bindings/CMakeLists.txt | 14 ++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/roboplan_oink/bindings/CMakeLists.txt b/roboplan_oink/bindings/CMakeLists.txt index a319ac27..4ca2c52f 100644 --- a/roboplan_oink/bindings/CMakeLists.txt +++ b/roboplan_oink/bindings/CMakeLists.txt @@ -42,6 +42,17 @@ target_link_libraries(_optimal_ik_ext PRIVATE roboplan_oink roboplan::roboplan ) +if(SKBUILD) + set_target_properties(_optimal_ik_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../lib" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +elseif(ROBOPLAN_CMEEL) + set_target_properties(_optimal_ik_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../../.." + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -54,6 +65,9 @@ target_link_libraries(_optimal_ik_ext PRIVATE if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") +elseif(ROBOPLAN_CMEEL) + set(PYTHON_DESTINATION + "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() diff --git a/roboplan_rrt/bindings/CMakeLists.txt b/roboplan_rrt/bindings/CMakeLists.txt index fd560163..1899fadb 100644 --- a/roboplan_rrt/bindings/CMakeLists.txt +++ b/roboplan_rrt/bindings/CMakeLists.txt @@ -41,6 +41,17 @@ target_link_libraries(_rrt_ext PRIVATE roboplan_rrt roboplan::roboplan ) +if(SKBUILD) + set_target_properties(_rrt_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../lib" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +elseif(ROBOPLAN_CMEEL) + set_target_properties(_rrt_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../../.." + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -53,6 +64,9 @@ target_link_libraries(_rrt_ext PRIVATE if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") +elseif(ROBOPLAN_CMEEL) + set(PYTHON_DESTINATION + "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() diff --git a/roboplan_simple_ik/bindings/CMakeLists.txt b/roboplan_simple_ik/bindings/CMakeLists.txt index 829088ba..973930e7 100644 --- a/roboplan_simple_ik/bindings/CMakeLists.txt +++ b/roboplan_simple_ik/bindings/CMakeLists.txt @@ -41,6 +41,17 @@ target_link_libraries(_simple_ik_ext PRIVATE roboplan_simple_ik roboplan::roboplan ) +if(SKBUILD) + set_target_properties(_simple_ik_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../lib" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +elseif(ROBOPLAN_CMEEL) + set_target_properties(_simple_ik_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../../.." + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -53,6 +64,9 @@ target_link_libraries(_simple_ik_ext PRIVATE if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") +elseif(ROBOPLAN_CMEEL) + set(PYTHON_DESTINATION + "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() diff --git a/roboplan_toppra/bindings/CMakeLists.txt b/roboplan_toppra/bindings/CMakeLists.txt index 3643bd44..6a10a745 100644 --- a/roboplan_toppra/bindings/CMakeLists.txt +++ b/roboplan_toppra/bindings/CMakeLists.txt @@ -42,6 +42,17 @@ target_link_libraries(_toppra_ext PRIVATE roboplan_toppra roboplan::roboplan ) +if(SKBUILD) + set_target_properties(_toppra_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../lib" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +elseif(ROBOPLAN_CMEEL) + set_target_properties(_toppra_ext PROPERTIES + INSTALL_RPATH "$ORIGIN/../../../.." + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -54,6 +65,9 @@ target_link_libraries(_toppra_ext PRIVATE if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") +elseif(ROBOPLAN_CMEEL) + set(PYTHON_DESTINATION + "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() From 67f0875b7750152ddb8c5d08aa0687934601f5c3 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 21:50:33 -0700 Subject: [PATCH 07/52] Add Python packaging structure tests Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- tests/test_unified_python_package.py | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/test_unified_python_package.py diff --git a/tests/test_unified_python_package.py b/tests/test_unified_python_package.py new file mode 100644 index 00000000..ba84baae --- /dev/null +++ b/tests/test_unified_python_package.py @@ -0,0 +1,116 @@ +from pathlib import Path +import re +import tomllib +from typing import cast + + +ROOT = Path(__file__).resolve().parents[1] + + +def _read(path: str) -> str: + return (ROOT / path).read_text(encoding="utf-8") + + +def _read_pyproject(path: str) -> dict[str, object]: + return cast(dict[str, object], tomllib.loads(_read(path))) + + +def test_root_pyproject_defines_unified_roboplan_distribution() -> None: + pyproject = ROOT / "pyproject.toml" + + assert pyproject.exists(), "root pyproject.toml is required for the unified roboplan wheel" + + data = cast(dict[str, object], tomllib.loads(pyproject.read_text(encoding="utf-8"))) + build_system = cast(dict[str, object], data["build-system"]) + build_requires = cast(list[str], build_system["requires"]) + project = cast(dict[str, object], data["project"]) + + assert project["name"] == "roboplan" + assert project["version"] == "0.4.0" + assert project["requires-python"] == ">=3.10" + assert "patchelf; platform_system == 'Linux'" in build_requires + dependencies = cast(list[str], project["dependencies"]) + assert "roboplan-core" not in dependencies + assert "numpy" in dependencies + assert "pin" in dependencies + assert "matplotlib" in dependencies + + tool = cast(dict[str, object], data["tool"]) + scikit_build = cast(dict[str, object], tool["scikit-build"]) + cmake = cast(dict[str, object], scikit_build["cmake"]) + defines = cast(dict[str, str], cmake["define"]) + assert defines["CMAKE_INSTALL_RPATH"] == "$ORIGIN/../../lib" + assert defines["CMAKE_INSTALL_RPATH_USE_LINK_PATH"] == "FALSE" + + sdist = cast(dict[str, list[str]], scikit_build["sdist"]) + assert ".omo/**" in sdist["exclude"] + + +def test_cmeel_split_packages_define_native_and_python_wheels() -> None: + libroboplan = _read_pyproject("packaging/cmeel/libroboplan/pyproject.toml") + roboplan = _read_pyproject("packaging/cmeel/roboplan/pyproject.toml") + + lib_build = cast(dict[str, object], libroboplan["build-system"]) + lib_project = cast(dict[str, object], libroboplan["project"]) + lib_tool = cast(dict[str, object], libroboplan["tool"]) + lib_cmeel = cast(dict[str, object], lib_tool["cmeel"]) + lib_args = cast(list[str], lib_cmeel["configure-args"]) + + assert lib_build["build-backend"] == "cmeel" + assert lib_project["name"] == "libroboplan" + assert lib_cmeel["source"] == "../../.." + assert lib_cmeel["has-sitelib"] is False + assert "-DROBOPLAN_CMEEL=ON" in lib_args + assert "-DBUILD_PYTHON_BINDINGS=OFF" in lib_args + + py_build = cast(dict[str, object], roboplan["build-system"]) + py_project = cast(dict[str, object], roboplan["project"]) + py_tool = cast(dict[str, object], roboplan["tool"]) + py_cmeel = cast(dict[str, object], py_tool["cmeel"]) + py_args = cast(list[str], py_cmeel["configure-args"]) + py_dependencies = cast(list[str], py_project["dependencies"]) + + assert py_build["build-backend"] == "cmeel" + assert py_project["name"] == "roboplan" + assert py_cmeel["source"] == "../../.." + assert "libroboplan == 0.4.0" in py_dependencies + assert "pin == 4.0.0" in py_dependencies + assert "-DROBOPLAN_CMEEL=ON" in py_args + assert "-DBUILD_STANDALONE_PYTHON_BINDINGS=ON" in py_args + assert "-DGENERATE_PYTHON_STUBS=OFF" in py_args + + +def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> None: + cmake = ROOT / "CMakeLists.txt" + + assert cmake.exists(), "root CMakeLists.txt is required for the unified roboplan wheel" + + source = cmake.read_text(encoding="utf-8") + package_order = re.findall(r"add_subdirectory\((roboplan(?:_[a-z_]+)?)\)", source) + + repair_source = _read("cmake/roboplan_unified_repair/CMakeLists.txt") + + assert "add_subdirectory(cmake/roboplan_unified_repair)" in source + assert "--set-rpath" in repair_source + assert "$ORIGIN/../../lib" in source + assert package_order == [ + "roboplan_example_models", + "roboplan", + "roboplan_simple_ik", + "roboplan_oink", + "roboplan_rrt", + "roboplan_toppra", + ] + + +def test_dependent_packages_can_use_in_tree_core_target() -> None: + for path in [ + "roboplan_simple_ik/CMakeLists.txt", + "roboplan_oink/CMakeLists.txt", + "roboplan_rrt/CMakeLists.txt", + "roboplan_toppra/CMakeLists.txt", + ]: + source = _read(path) + + assert "if(NOT TARGET roboplan::roboplan)" in source, path + assert "find_package(roboplan REQUIRED)" in source, path From 49fa86a0cc6d1667f9be4668bb185569f67753da Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 8 Jun 2026 22:43:22 -0700 Subject: [PATCH 08/52] Support VCS source builds with cmeel dependencies Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- CMakeLists.txt | 30 +++++++++++++++++++++++++++- pyproject.toml | 4 ++++ tests/test_unified_python_package.py | 12 +++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2c236f9..2f13b96a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,21 @@ option( OFF ) +if(SKBUILD) + find_package(Python COMPONENTS Interpreter REQUIRED) + execute_process( + COMMAND + "${Python_EXECUTABLE}" + -c + "import pathlib, sysconfig; prefix = pathlib.Path(sysconfig.get_path('purelib')) / 'cmeel.prefix'; print(prefix if prefix.exists() else '')" + OUTPUT_VARIABLE ROBOPLAN_CMEEL_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(ROBOPLAN_CMEEL_PREFIX) + list(PREPEND CMAKE_PREFIX_PATH "${ROBOPLAN_CMEEL_PREFIX}") + endif() +endif() + if(BUILD_STANDALONE_PYTHON_BINDINGS) find_package(roboplan_example_models REQUIRED CONFIG) find_package(roboplan REQUIRED CONFIG) @@ -86,6 +101,7 @@ if(SKBUILD) foreach(library IN ITEMS OsqpEigen + boost_atomic boost_filesystem boost_serialization boost_system @@ -119,9 +135,13 @@ if(SKBUILD) foreach(library IN ITEMS libassimp.so.6 + libboost_atomic.so.1.90.0 libboost_filesystem.so.1.88.0 + libboost_filesystem.so.1.90.0 libboost_serialization.so.1.88.0 + libboost_serialization.so.1.90.0 libboost_system.so.1.88.0 + libconsole_bridge.so.1.0 libgz-math.so.9 libgz-utils.so.4 liboctomap.so.1.10 @@ -129,9 +149,12 @@ if(SKBUILD) libqhull_r.so.8.0 libsdformat.so.16 libtinyxml2.so.11 + liburdfdom_model.so.6 + liburdfdom_sensor.so.6 + liburdfdom_world.so.6 libyaml-cpp.so.0.8) find_file(ROBOPLAN_UNIFIED_${library}_FILE NAMES ${library} - PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" + PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" "${ROBOPLAN_CMEEL_PREFIX}/lib" NO_DEFAULT_PATH) if(ROBOPLAN_UNIFIED_${library}_FILE) install(FILES "${ROBOPLAN_UNIFIED_${library}_FILE}" DESTINATION lib) @@ -140,6 +163,11 @@ if(SKBUILD) endif() endforeach() + find_library(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY NAMES yaml-cpp) + if(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY) + install(FILES "${ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY}" DESTINATION lib RENAME libyaml-cpp.so.0.8) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_subdirectory(cmake/roboplan_unified_repair) endif() diff --git a/pyproject.toml b/pyproject.toml index 0941aed8..cb6efae6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,9 @@ [build-system] requires = [ "scikit-build-core >=0.10", + "cmeel-eigen[build]", + "cmeel-yaml-cpp[build]", + "libpinocchio[build] == 4.0.0", "nanobind >=1.3.2", "patchelf; platform_system == 'Linux'", ] @@ -34,6 +37,7 @@ build-dir = "build/{wheel_tag}" cmake.define.BUILD_PYTHON_BINDINGS = "ON" cmake.define.BUILD_TESTING = "OFF" cmake.define.GENERATE_PYTHON_STUBS = "OFF" +cmake.define.CMAKE_POLICY_VERSION_MINIMUM = "3.5" cmake.define.CMAKE_INSTALL_RPATH = "$ORIGIN/../../lib" cmake.define.CMAKE_INSTALL_RPATH_USE_LINK_PATH = "FALSE" diff --git a/tests/test_unified_python_package.py b/tests/test_unified_python_package.py index ba84baae..37bfefac 100644 --- a/tests/test_unified_python_package.py +++ b/tests/test_unified_python_package.py @@ -28,6 +28,9 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: assert project["name"] == "roboplan" assert project["version"] == "0.4.0" assert project["requires-python"] == ">=3.10" + assert "cmeel-eigen[build]" in build_requires + assert "cmeel-yaml-cpp[build]" in build_requires + assert "libpinocchio[build] == 4.0.0" in build_requires assert "patchelf; platform_system == 'Linux'" in build_requires dependencies = cast(list[str], project["dependencies"]) assert "roboplan-core" not in dependencies @@ -39,6 +42,7 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: scikit_build = cast(dict[str, object], tool["scikit-build"]) cmake = cast(dict[str, object], scikit_build["cmake"]) defines = cast(dict[str, str], cmake["define"]) + assert defines["CMAKE_POLICY_VERSION_MINIMUM"] == "3.5" assert defines["CMAKE_INSTALL_RPATH"] == "$ORIGIN/../../lib" assert defines["CMAKE_INSTALL_RPATH_USE_LINK_PATH"] == "FALSE" @@ -91,6 +95,14 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No repair_source = _read("cmake/roboplan_unified_repair/CMakeLists.txt") assert "add_subdirectory(cmake/roboplan_unified_repair)" in source + assert "cmeel.prefix" in source + assert "list(PREPEND CMAKE_PREFIX_PATH" in source + assert "${ROBOPLAN_CMEEL_PREFIX}/lib" in source + assert "RENAME libyaml-cpp.so.0.8" in source + assert "boost_atomic" in source + assert "libboost_atomic.so.1.90.0" in source + assert "libboost_filesystem.so.1.90.0" in source + assert "liburdfdom_world.so.6" in source assert "--set-rpath" in repair_source assert "$ORIGIN/../../lib" in source assert package_order == [ From 00be2a037b3d4d8352b21dd44950f1dcfd279fa8 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 16 Jun 2026 09:40:06 -0700 Subject: [PATCH 09/52] Fix PR CI packaging build modes Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan/bindings/CMakeLists.txt | 16 ++++--- roboplan/test/CMakeLists.txt | 8 +++- .../bindings/CMakeLists.txt | 8 ++-- roboplan_oink/bindings/CMakeLists.txt | 8 ++-- roboplan_oink/test/CMakeLists.txt | 8 +++- roboplan_rrt/bindings/CMakeLists.txt | 8 ++-- roboplan_rrt/test/CMakeLists.txt | 8 +++- roboplan_simple_ik/bindings/CMakeLists.txt | 8 ++-- roboplan_toppra/bindings/CMakeLists.txt | 8 ++-- roboplan_toppra/test/CMakeLists.txt | 8 +++- ...kage.py => unified_python_package_test.py} | 45 +++++++++++++++++-- 11 files changed, 105 insertions(+), 28 deletions(-) rename tests/{test_unified_python_package.py => unified_python_package_test.py} (75%) diff --git a/roboplan/bindings/CMakeLists.txt b/roboplan/bindings/CMakeLists.txt index 2a542ffc..585ebfdd 100644 --- a/roboplan/bindings/CMakeLists.txt +++ b/roboplan/bindings/CMakeLists.txt @@ -29,12 +29,16 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -# Always resolve nanobind from the active Python interpreter so we pick up the -# environment's (pixi/wheel build-env) nanobind rather than a stray system -# install (e.g. /usr/share/nanobind from apt, which lacks bundled headers). -execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +# In non-ament builds, resolve nanobind from the active Python interpreter so we +# pick up the environment's (pixi/wheel build-env) nanobind rather than a stray +# system install (e.g. /usr/share/nanobind from apt, which lacks bundled +# headers). Ament builds may only have nanobind-dev installed, without the Python +# module, so rely on find_package() there. +if(NOT AMENT_BUILD) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +endif() find_package(nanobind CONFIG REQUIRED) # NOTE: the shared binding utility header lives diff --git a/roboplan/test/CMakeLists.txt b/roboplan/test/CMakeLists.txt index 0a8ede06..052c6268 100644 --- a/roboplan/test/CMakeLists.txt +++ b/roboplan/test/CMakeLists.txt @@ -1,5 +1,11 @@ find_package(GTest REQUIRED) -find_package(roboplan_example_models REQUIRED) +if(NOT TARGET roboplan_example_models::roboplan_example_models) + if(TARGET roboplan_example_models) + add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) + else() + find_package(roboplan_example_models REQUIRED) + endif() +endif() # Build tests add_executable(test_types test_types.cpp) diff --git a/roboplan_example_models/bindings/CMakeLists.txt b/roboplan_example_models/bindings/CMakeLists.txt index 89ee2bd7..a6780681 100644 --- a/roboplan_example_models/bindings/CMakeLists.txt +++ b/roboplan_example_models/bindings/CMakeLists.txt @@ -19,9 +19,11 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(NOT AMENT_BUILD) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +endif() find_package(nanobind CONFIG REQUIRED) nanobind_add_module( diff --git a/roboplan_oink/bindings/CMakeLists.txt b/roboplan_oink/bindings/CMakeLists.txt index 4ca2c52f..8f0b9f04 100644 --- a/roboplan_oink/bindings/CMakeLists.txt +++ b/roboplan_oink/bindings/CMakeLists.txt @@ -21,9 +21,11 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(NOT AMENT_BUILD) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +endif() find_package(nanobind CONFIG REQUIRED) nanobind_add_module( diff --git a/roboplan_oink/test/CMakeLists.txt b/roboplan_oink/test/CMakeLists.txt index c4eba865..488b0140 100644 --- a/roboplan_oink/test/CMakeLists.txt +++ b/roboplan_oink/test/CMakeLists.txt @@ -1,5 +1,11 @@ find_package(GTest REQUIRED) -find_package(roboplan_example_models REQUIRED) +if(NOT TARGET roboplan_example_models::roboplan_example_models) + if(TARGET roboplan_example_models) + add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) + else() + find_package(roboplan_example_models REQUIRED) + endif() +endif() # Build Oink integration tests add_executable(test_oink test_oink.cpp) diff --git a/roboplan_rrt/bindings/CMakeLists.txt b/roboplan_rrt/bindings/CMakeLists.txt index 1899fadb..f75dfc86 100644 --- a/roboplan_rrt/bindings/CMakeLists.txt +++ b/roboplan_rrt/bindings/CMakeLists.txt @@ -20,9 +20,11 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(NOT AMENT_BUILD) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +endif() find_package(nanobind CONFIG REQUIRED) nanobind_add_module( diff --git a/roboplan_rrt/test/CMakeLists.txt b/roboplan_rrt/test/CMakeLists.txt index 7742c3ed..6163cfb5 100644 --- a/roboplan_rrt/test/CMakeLists.txt +++ b/roboplan_rrt/test/CMakeLists.txt @@ -1,5 +1,11 @@ find_package(GTest REQUIRED) -find_package(roboplan_example_models REQUIRED) +if(NOT TARGET roboplan_example_models::roboplan_example_models) + if(TARGET roboplan_example_models) + add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) + else() + find_package(roboplan_example_models REQUIRED) + endif() +endif() # Build tests add_executable(test_rrt test_rrt.cpp) diff --git a/roboplan_simple_ik/bindings/CMakeLists.txt b/roboplan_simple_ik/bindings/CMakeLists.txt index 973930e7..1036cf25 100644 --- a/roboplan_simple_ik/bindings/CMakeLists.txt +++ b/roboplan_simple_ik/bindings/CMakeLists.txt @@ -20,9 +20,11 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(NOT AMENT_BUILD) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +endif() find_package(nanobind CONFIG REQUIRED) nanobind_add_module( diff --git a/roboplan_toppra/bindings/CMakeLists.txt b/roboplan_toppra/bindings/CMakeLists.txt index 6a10a745..8c8ea8d6 100644 --- a/roboplan_toppra/bindings/CMakeLists.txt +++ b/roboplan_toppra/bindings/CMakeLists.txt @@ -21,9 +21,11 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(NOT AMENT_BUILD) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +endif() find_package(nanobind CONFIG REQUIRED) nanobind_add_module( diff --git a/roboplan_toppra/test/CMakeLists.txt b/roboplan_toppra/test/CMakeLists.txt index d035748b..25071901 100644 --- a/roboplan_toppra/test/CMakeLists.txt +++ b/roboplan_toppra/test/CMakeLists.txt @@ -1,5 +1,11 @@ find_package(GTest REQUIRED) -find_package(roboplan_example_models REQUIRED) +if(NOT TARGET roboplan_example_models::roboplan_example_models) + if(TARGET roboplan_example_models) + add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) + else() + find_package(roboplan_example_models REQUIRED) + endif() +endif() # Build tests add_executable(test_toppra test_toppra.cpp) diff --git a/tests/test_unified_python_package.py b/tests/unified_python_package_test.py similarity index 75% rename from tests/test_unified_python_package.py rename to tests/unified_python_package_test.py index 37bfefac..c16f0f2c 100644 --- a/tests/test_unified_python_package.py +++ b/tests/unified_python_package_test.py @@ -3,7 +3,6 @@ import tomllib from typing import cast - ROOT = Path(__file__).resolve().parents[1] @@ -18,7 +17,9 @@ def _read_pyproject(path: str) -> dict[str, object]: def test_root_pyproject_defines_unified_roboplan_distribution() -> None: pyproject = ROOT / "pyproject.toml" - assert pyproject.exists(), "root pyproject.toml is required for the unified roboplan wheel" + assert ( + pyproject.exists() + ), "root pyproject.toml is required for the unified roboplan wheel" data = cast(dict[str, object], tomllib.loads(pyproject.read_text(encoding="utf-8"))) build_system = cast(dict[str, object], data["build-system"]) @@ -87,7 +88,9 @@ def test_cmeel_split_packages_define_native_and_python_wheels() -> None: def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> None: cmake = ROOT / "CMakeLists.txt" - assert cmake.exists(), "root CMakeLists.txt is required for the unified roboplan wheel" + assert ( + cmake.exists() + ), "root CMakeLists.txt is required for the unified roboplan wheel" source = cmake.read_text(encoding="utf-8") package_order = re.findall(r"add_subdirectory\((roboplan(?:_[a-z_]+)?)\)", source) @@ -126,3 +129,39 @@ def test_dependent_packages_can_use_in_tree_core_target() -> None: assert "if(NOT TARGET roboplan::roboplan)" in source, path assert "find_package(roboplan REQUIRED)" in source, path + + +def test_binding_packages_preserve_python_nanobind_for_non_ament_builds() -> None: + for path in [ + "roboplan/bindings/CMakeLists.txt", + "roboplan_example_models/bindings/CMakeLists.txt", + "roboplan_simple_ik/bindings/CMakeLists.txt", + "roboplan_oink/bindings/CMakeLists.txt", + "roboplan_rrt/bindings/CMakeLists.txt", + "roboplan_toppra/bindings/CMakeLists.txt", + ]: + source = _read(path) + + assert "if(NOT AMENT_BUILD)" in source, path + assert "-m nanobind --cmake_dir" in source, path + assert "find_package(nanobind CONFIG REQUIRED)" in source, path + + +def test_dependent_package_tests_can_use_in_tree_example_models_target() -> None: + for path in [ + "roboplan/test/CMakeLists.txt", + "roboplan_oink/test/CMakeLists.txt", + "roboplan_rrt/test/CMakeLists.txt", + "roboplan_toppra/test/CMakeLists.txt", + ]: + source = _read(path) + + assert ( + "if(NOT TARGET roboplan_example_models::roboplan_example_models)" in source + ), path + assert "if(TARGET roboplan_example_models)" in source, path + assert ( + "add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models)" + in source + ), path + assert "find_package(roboplan_example_models REQUIRED)" in source, path From 0ca01175f5eb41e0b5ce4b2ac26d472cb135472d Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 16 Jun 2026 10:03:18 -0700 Subject: [PATCH 10/52] Prefer Python nanobind discovery in ROS builds Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan/bindings/CMakeLists.txt | 22 +++++++++++-------- .../bindings/CMakeLists.txt | 13 +++++++---- roboplan_oink/bindings/CMakeLists.txt | 13 +++++++---- roboplan_rrt/bindings/CMakeLists.txt | 13 +++++++---- roboplan_simple_ik/bindings/CMakeLists.txt | 13 +++++++---- roboplan_toppra/bindings/CMakeLists.txt | 13 +++++++---- tests/unified_python_package_test.py | 3 ++- 7 files changed, 60 insertions(+), 30 deletions(-) diff --git a/roboplan/bindings/CMakeLists.txt b/roboplan/bindings/CMakeLists.txt index 585ebfdd..ff602243 100644 --- a/roboplan/bindings/CMakeLists.txt +++ b/roboplan/bindings/CMakeLists.txt @@ -29,15 +29,19 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -# In non-ament builds, resolve nanobind from the active Python interpreter so we -# pick up the environment's (pixi/wheel build-env) nanobind rather than a stray -# system install (e.g. /usr/share/nanobind from apt, which lacks bundled -# headers). Ament builds may only have nanobind-dev installed, without the Python -# module, so rely on find_package() there. -if(NOT AMENT_BUILD) - execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +# Prefer nanobind from the active Python interpreter when available. This keeps +# pixi/wheel builds on the isolated build environment's nanobind while allowing +# ament builds to fall back to a system CMake package when rosdep provides +# nanobind-dev without a Python module. +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) + message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") +endif() +if(ROBOPLAN_NANOBIND_PYTHON_RESULT) + unset(nanobind_ROOT) endif() find_package(nanobind CONFIG REQUIRED) diff --git a/roboplan_example_models/bindings/CMakeLists.txt b/roboplan_example_models/bindings/CMakeLists.txt index a6780681..3ae3a536 100644 --- a/roboplan_example_models/bindings/CMakeLists.txt +++ b/roboplan_example_models/bindings/CMakeLists.txt @@ -19,10 +19,15 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -if(NOT AMENT_BUILD) - execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) + message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") +endif() +if(ROBOPLAN_NANOBIND_PYTHON_RESULT) + unset(nanobind_ROOT) endif() find_package(nanobind CONFIG REQUIRED) diff --git a/roboplan_oink/bindings/CMakeLists.txt b/roboplan_oink/bindings/CMakeLists.txt index 8f0b9f04..0af0bcd4 100644 --- a/roboplan_oink/bindings/CMakeLists.txt +++ b/roboplan_oink/bindings/CMakeLists.txt @@ -21,10 +21,15 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -if(NOT AMENT_BUILD) - execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) + message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") +endif() +if(ROBOPLAN_NANOBIND_PYTHON_RESULT) + unset(nanobind_ROOT) endif() find_package(nanobind CONFIG REQUIRED) diff --git a/roboplan_rrt/bindings/CMakeLists.txt b/roboplan_rrt/bindings/CMakeLists.txt index f75dfc86..05989580 100644 --- a/roboplan_rrt/bindings/CMakeLists.txt +++ b/roboplan_rrt/bindings/CMakeLists.txt @@ -20,10 +20,15 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -if(NOT AMENT_BUILD) - execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) + message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") +endif() +if(ROBOPLAN_NANOBIND_PYTHON_RESULT) + unset(nanobind_ROOT) endif() find_package(nanobind CONFIG REQUIRED) diff --git a/roboplan_simple_ik/bindings/CMakeLists.txt b/roboplan_simple_ik/bindings/CMakeLists.txt index 1036cf25..2d37f26c 100644 --- a/roboplan_simple_ik/bindings/CMakeLists.txt +++ b/roboplan_simple_ik/bindings/CMakeLists.txt @@ -20,10 +20,15 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -if(NOT AMENT_BUILD) - execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) + message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") +endif() +if(ROBOPLAN_NANOBIND_PYTHON_RESULT) + unset(nanobind_ROOT) endif() find_package(nanobind CONFIG REQUIRED) diff --git a/roboplan_toppra/bindings/CMakeLists.txt b/roboplan_toppra/bindings/CMakeLists.txt index 8c8ea8d6..c01e4d82 100644 --- a/roboplan_toppra/bindings/CMakeLists.txt +++ b/roboplan_toppra/bindings/CMakeLists.txt @@ -21,10 +21,15 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -if(NOT AMENT_BUILD) - execute_process( - COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) + message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") +endif() +if(ROBOPLAN_NANOBIND_PYTHON_RESULT) + unset(nanobind_ROOT) endif() find_package(nanobind CONFIG REQUIRED) diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index c16f0f2c..38bac2cb 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -142,8 +142,9 @@ def test_binding_packages_preserve_python_nanobind_for_non_ament_builds() -> Non ]: source = _read(path) - assert "if(NOT AMENT_BUILD)" in source, path assert "-m nanobind --cmake_dir" in source, path + assert "ROBOPLAN_NANOBIND_PYTHON_RESULT" in source, path + assert "AND NOT AMENT_BUILD" in source, path assert "find_package(nanobind CONFIG REQUIRED)" in source, path From 40fd08a4853e6163b9f84482f17a83e9b4f00c91 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 16 Jun 2026 10:55:26 -0700 Subject: [PATCH 11/52] Add hpp-fcl target compatibility Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- CMakeLists.txt | 21 ++++++++++++++++ roboplan/CMakeLists.txt | 33 ++++++++++++++++++++++++++ roboplan/cmake/roboplanConfig.cmake.in | 33 ++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f13b96a..2b2b1feb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,27 @@ if(SKBUILD) endif() endif() +find_package(hpp-fcl CONFIG QUIET) +if(NOT TARGET hpp-fcl::hpp-fcl) + find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR + NAMES hpp/fcl/fwd.hh + HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" + ) + find_library(ROBOPLAN_HPP_FCL_LIBRARY + NAMES hpp-fcl + HINTS + "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "/opt/ros/$ENV{ROS_DISTRO}/lib" + ) + if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) + add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) + set_target_properties(hpp-fcl::hpp-fcl PROPERTIES + IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" + ) + endif() +endif() + if(BUILD_STANDALONE_PYTHON_BINDINGS) find_package(roboplan_example_models REQUIRED CONFIG) find_package(roboplan REQUIRED CONFIG) diff --git a/roboplan/CMakeLists.txt b/roboplan/CMakeLists.txt index 15b38c21..edb2609c 100644 --- a/roboplan/CMakeLists.txt +++ b/roboplan/CMakeLists.txt @@ -8,6 +8,39 @@ endif() # Find dependencies. find_package(Eigen3 REQUIRED) find_package(pinocchio REQUIRED) +find_package(hpp-fcl CONFIG QUIET) +if(NOT TARGET hpp-fcl::hpp-fcl AND DEFINED pinocchio_DIR) + get_filename_component(ROBOPLAN_PINOCCHIO_CMAKE_DIR "${pinocchio_DIR}" DIRECTORY) + find_package(hpp-fcl CONFIG REQUIRED + PATHS "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" + NO_DEFAULT_PATH + ) +endif() +if(NOT TARGET hpp-fcl::hpp-fcl) + find_package(hpp-fcl CONFIG QUIET) +endif() +if(NOT TARGET hpp-fcl::hpp-fcl) + find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR + NAMES hpp/fcl/fwd.hh + HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" + ) + find_library(ROBOPLAN_HPP_FCL_LIBRARY + NAMES hpp-fcl + HINTS + "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "/opt/ros/$ENV{ROS_DISTRO}/lib" + ) + if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) + add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) + set_target_properties(hpp-fcl::hpp-fcl PROPERTIES + IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" + ) + endif() +endif() +if(NOT TARGET hpp-fcl::hpp-fcl) + message(FATAL_ERROR "Failed to resolve hpp-fcl::hpp-fcl") +endif() find_package(console_bridge REQUIRED) find_package(yaml-cpp REQUIRED) diff --git a/roboplan/cmake/roboplanConfig.cmake.in b/roboplan/cmake/roboplanConfig.cmake.in index c6e7eead..2e900bdd 100644 --- a/roboplan/cmake/roboplanConfig.cmake.in +++ b/roboplan/cmake/roboplanConfig.cmake.in @@ -2,6 +2,39 @@ find_package(Eigen3 REQUIRED) find_package(pinocchio REQUIRED) +find_package(hpp-fcl CONFIG QUIET) +if(NOT TARGET hpp-fcl::hpp-fcl AND DEFINED pinocchio_DIR) + get_filename_component(ROBOPLAN_PINOCCHIO_CMAKE_DIR "${pinocchio_DIR}" DIRECTORY) + find_package(hpp-fcl CONFIG REQUIRED + PATHS "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" + NO_DEFAULT_PATH + ) +endif() +if(NOT TARGET hpp-fcl::hpp-fcl) + find_package(hpp-fcl CONFIG QUIET) +endif() +if(NOT TARGET hpp-fcl::hpp-fcl) + find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR + NAMES hpp/fcl/fwd.hh + HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" + ) + find_library(ROBOPLAN_HPP_FCL_LIBRARY + NAMES hpp-fcl + HINTS + "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "/opt/ros/$ENV{ROS_DISTRO}/lib" + ) + if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) + add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) + set_target_properties(hpp-fcl::hpp-fcl PROPERTIES + IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" + ) + endif() +endif() +if(NOT TARGET hpp-fcl::hpp-fcl) + message(FATAL_ERROR "Failed to resolve hpp-fcl::hpp-fcl") +endif() find_package(tinyxml2_vendor QUIET) find_package(TinyXML2 QUIET) find_package(console_bridge QUIET) From cf83c12d03f6cf73fc5e5bb7b8e56d705f64a243 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 16 Jun 2026 10:55:32 -0700 Subject: [PATCH 12/52] Cover hpp-fcl compatibility contract Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- tests/unified_python_package_test.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 38bac2cb..a815df48 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -51,6 +51,40 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: assert ".omo/**" in sdist["exclude"] +def test_core_cmake_finds_pinocchio_transitive_targets_explicitly() -> None: + root = _read("CMakeLists.txt") + source = _read("roboplan/CMakeLists.txt") + config = _read("roboplan/cmake/roboplanConfig.cmake.in") + + assert "find_package(hpp-fcl CONFIG QUIET)" in root + assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in root + assert root.index("find_package(hpp-fcl CONFIG QUIET)") < root.index( + "add_subdirectory(roboplan)" + ) + + assert "find_package(pinocchio REQUIRED)" in source + assert "find_package(hpp-fcl CONFIG QUIET)" in source + assert "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" in source + assert "Failed to resolve hpp-fcl::hpp-fcl" in source + assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in source + assert "hpp/fcl/fwd.hh" in source + assert "NAMES hpp-fcl" in source + assert source.index("find_package(pinocchio REQUIRED)") < source.index( + "find_package(hpp-fcl CONFIG QUIET)" + ) + assert source.index("find_package(hpp-fcl CONFIG QUIET)") < source.index( + "target_link_libraries(roboplan" + ) + + assert "find_package(pinocchio REQUIRED)" in config + assert "find_package(hpp-fcl CONFIG QUIET)" in config + assert "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" in config + assert "Failed to resolve hpp-fcl::hpp-fcl" in config + assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in config + assert "hpp/fcl/fwd.hh" in config + assert "NAMES hpp-fcl" in config + + def test_cmeel_split_packages_define_native_and_python_wheels() -> None: libroboplan = _read_pyproject("packaging/cmeel/libroboplan/pyproject.toml") roboplan = _read_pyproject("packaging/cmeel/roboplan/pyproject.toml") From c1fb58230c1c076c8e9a07edeededf2df44233f5 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 16 Jun 2026 10:58:56 -0700 Subject: [PATCH 13/52] Drop Rolling from required ROS CI Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .github/workflows/build_and_test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index b34d2cf4..8eafb296 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -68,7 +68,10 @@ jobs: strategy: fail-fast: false matrix: - ros_distro: [humble, jazzy, kilted, lyrical, rolling] + # Rolling is excluded from required PR CI while upstream Rolling Docker + # images and rosdep metadata are between Ubuntu platform migrations. + # Lyrical keeps Ubuntu 26.04 ROS coverage in this matrix. + ros_distro: [humble, jazzy, kilted, lyrical] runs-on: ubuntu-latest steps: From d24724c2be32efd8f757479222a897e92a4aabdd Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 16 Jun 2026 11:09:43 -0700 Subject: [PATCH 14/52] Revert "Drop Rolling from required ROS CI" This reverts commit 7561f6db0a96fdb6d9076cbc4fa711c84aad68d9. --- .github/workflows/build_and_test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 8eafb296..b34d2cf4 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -68,10 +68,7 @@ jobs: strategy: fail-fast: false matrix: - # Rolling is excluded from required PR CI while upstream Rolling Docker - # images and rosdep metadata are between Ubuntu platform migrations. - # Lyrical keeps Ubuntu 26.04 ROS coverage in this matrix. - ros_distro: [humble, jazzy, kilted, lyrical] + ros_distro: [humble, jazzy, kilted, lyrical, rolling] runs-on: ubuntu-latest steps: From 74a6093e31d75c6eb006001b3e88065271c5b0dd Mon Sep 17 00:00:00 2001 From: cc Date: Fri, 19 Jun 2026 19:19:27 -0700 Subject: [PATCH 15/52] Rename unified distribution to roboplan-dimos Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- pyproject.toml | 2 +- tests/unified_python_package_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cb6efae6..9f67883f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ requires = [ build-backend = "scikit_build_core.build" [project] -name = "roboplan" +name = "roboplan-dimos" version = "0.4.0" description = "Modern robot motion planning library based on Pinocchio." readme = "README.md" diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index a815df48..3f298629 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -26,7 +26,7 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: build_requires = cast(list[str], build_system["requires"]) project = cast(dict[str, object], data["project"]) - assert project["name"] == "roboplan" + assert project["name"] == "roboplan-dimos" assert project["version"] == "0.4.0" assert project["requires-python"] == ">=3.10" assert "cmeel-eigen[build]" in build_requires From 1679617a6f18b89e5b4427c954b3d996e8388c34 Mon Sep 17 00:00:00 2001 From: cc Date: Fri, 19 Jun 2026 19:20:01 -0700 Subject: [PATCH 16/52] Add PyPI release publishing workflow Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .github/workflows/release.yml | 181 +++++++++++++++++++++++++++ .gitignore | 4 + tests/unified_python_package_test.py | 21 ++++ 3 files changed, 206 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..d47bf620 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,181 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + publish_target: + description: "Publish destination after building artifacts" + type: choice + options: + - none + - testpypi + - pypi + default: testpypi + +permissions: + contents: read + +env: + PACKAGE_NAME: roboplan-dimos + CMAKE_BUILD_PARALLEL_LEVEL: "2" + MAKEFLAGS: "-j2" + NINJAFLAGS: "-j2" + +jobs: + check_version: + name: Check package version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.package.outputs.version }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + - name: Read package metadata + id: package + run: | + python - <<'PY' >> "$GITHUB_OUTPUT" + import tomllib + from pathlib import Path + + pyproject = tomllib.loads(Path("pyproject.toml").read_text()) + project = pyproject["project"] + if project["name"] != "roboplan-dimos": + raise SystemExit(f"Unexpected package name: {project['name']}") + print(f"version={project['version']}") + PY + - name: Check tag matches package version + if: startsWith(github.ref, 'refs/tags/v') + env: + PACKAGE_VERSION: ${{ steps.package.outputs.version }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + TAG_VERSION="${RELEASE_TAG#v}" + if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + echo "::error::Tag $RELEASE_TAG does not match package version $PACKAGE_VERSION" + exit 1 + fi + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + needs: check_version + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Build sdist + run: | + rm -rf dist + python -m pip install --upgrade build twine + python -m build --sdist + python -m twine check dist/*.tar.gz + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: python-distributions-sdist + path: dist/roboplan_dimos-*.tar.gz + if-no-files-found: error + + build_wheels: + name: Build Linux wheels + runs-on: ubuntu-latest + needs: check_version + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + - name: Build wheels + uses: pypa/cibuildwheel@v4.1.0 + env: + CIBW_ARCHS_LINUX: x86_64 + CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*" + CIBW_SKIP: "pp* *-musllinux*" + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_ENVIRONMENT: >- + CMAKE_BUILD_PARALLEL_LEVEL=2 + MAKEFLAGS="-j2" + NINJAFLAGS="-j2" + CIBW_TEST_COMMAND: >- + python -c "import roboplan.core; import roboplan.filters; import roboplan.example_models; + import roboplan.simple_ik; import roboplan.optimal_ik; import roboplan.rrt; + import roboplan.toppra; print('roboplan-dimos imports ok')" + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: python-distributions-linux + path: wheelhouse/roboplan_dimos-*.whl + if-no-files-found: error + + publish_testpypi: + name: Publish to TestPyPI + runs-on: ubuntu-latest + needs: + - build_sdist + - build_wheels + if: github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi' + environment: + name: testpypi + url: https://test.pypi.org/project/roboplan-dimos/ + permissions: + contents: read + id-token: write + steps: + - name: Download distributions + uses: actions/download-artifact@v5 + with: + pattern: python-distributions-* + merge-multiple: true + path: dist + - name: Check distributions + run: | + python -m pip install --upgrade twine + python -m twine check dist/* + - name: Publish to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + repository-url: https://test.pypi.org/legacy/ + skip-existing: true + + publish_pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: + - build_sdist + - build_wheels + if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') + environment: + name: pypi + url: https://pypi.org/project/roboplan-dimos/ + permissions: + contents: read + id-token: write + steps: + - name: Download distributions + uses: actions/download-artifact@v5 + with: + pattern: python-distributions-* + merge-multiple: true + path: dist + - name: Check distributions + run: | + python -m pip install --upgrade twine + python -m twine check dist/* + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ diff --git a/.gitignore b/.gitignore index 17041894..184f5e2f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,9 @@ .pixi *.egg-info +# Python packaging artifacts +dist/ +wheelhouse/ + # Benchmarking results pytest_benchmarks.json diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 3f298629..fbb0828a 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -200,3 +200,24 @@ def test_dependent_package_tests_can_use_in_tree_example_models_target() -> None in source ), path assert "find_package(roboplan_example_models REQUIRED)" in source, path + + +def test_release_workflow_builds_repaired_wheels_and_uses_trusted_publishing() -> None: + workflow = _read(".github/workflows/release.yml") + + assert "PACKAGE_NAME: roboplan-dimos" in workflow + assert "pypa/cibuildwheel@" in workflow + assert "CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28" in workflow + assert "CIBW_ARCHS_LINUX: x86_64" in workflow + assert 'CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*"' in workflow + assert 'CIBW_SKIP: "pp* *-musllinux*"' in workflow + assert "CMAKE_BUILD_PARALLEL_LEVEL=2" in workflow + assert 'MAKEFLAGS="-j2"' in workflow + assert 'NINJAFLAGS="-j2"' in workflow + assert "roboplan.core" in workflow + assert "roboplan.toppra" in workflow + assert "pypa/gh-action-pypi-publish@release/v1" in workflow + assert "id-token: write" in workflow + assert "repository-url: https://test.pypi.org/legacy/" in workflow + assert "TWINE_PASSWORD" not in workflow + assert "__token__" not in workflow From 2e449e83ec3d1667cfc09432ef682ed8584f177f Mon Sep 17 00:00:00 2001 From: cc Date: Fri, 19 Jun 2026 19:34:21 -0700 Subject: [PATCH 17/52] Install wheel libraries under lib for auditwheel Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- pyproject.toml | 1 + tests/unified_python_package_test.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9f67883f..bd5e634a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ cmake.define.BUILD_PYTHON_BINDINGS = "ON" cmake.define.BUILD_TESTING = "OFF" cmake.define.GENERATE_PYTHON_STUBS = "OFF" cmake.define.CMAKE_POLICY_VERSION_MINIMUM = "3.5" +cmake.define.CMAKE_INSTALL_LIBDIR = "lib" cmake.define.CMAKE_INSTALL_RPATH = "$ORIGIN/../../lib" cmake.define.CMAKE_INSTALL_RPATH_USE_LINK_PATH = "FALSE" diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index fbb0828a..047b3721 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -44,6 +44,7 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: cmake = cast(dict[str, object], scikit_build["cmake"]) defines = cast(dict[str, str], cmake["define"]) assert defines["CMAKE_POLICY_VERSION_MINIMUM"] == "3.5" + assert defines["CMAKE_INSTALL_LIBDIR"] == "lib" assert defines["CMAKE_INSTALL_RPATH"] == "$ORIGIN/../../lib" assert defines["CMAKE_INSTALL_RPATH_USE_LINK_PATH"] == "FALSE" From 291983986f1acaea56d34487d2c032ab77db49f9 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:05:02 -0700 Subject: [PATCH 18/52] Add contained Python packaging helpers Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/python/README.md | 17 ++ .../python/cmake/repair_cmeel_rpaths.cmake.in | 49 +++++ .../cmake/repair_unified_rpaths.cmake.in | 35 ++++ .../cmake/roboplan_python_packaging.cmake | 190 ++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 packaging/python/README.md create mode 100644 packaging/python/cmake/repair_cmeel_rpaths.cmake.in create mode 100644 packaging/python/cmake/repair_unified_rpaths.cmake.in create mode 100644 packaging/python/cmake/roboplan_python_packaging.cmake diff --git a/packaging/python/README.md b/packaging/python/README.md new file mode 100644 index 00000000..61b9aa1d --- /dev/null +++ b/packaging/python/README.md @@ -0,0 +1,17 @@ +# Python packaging + +This directory contains the PyPI/cibuildwheel-specific packaging helpers for +RoboPlan. The normal ROS/ament and source-tree CMake build remains in the +package directories; the root `CMakeLists.txt` includes these helpers only to +configure Python wheel behavior when `SKBUILD` or cmeel packaging is active. + +The helper CMake files cover three packaging-only concerns: + +- expose cmeel's isolated native dependency prefix to scikit-build-core builds; +- install the runtime native libraries that Linux wheels need at import time; +- repair installed RPATHs with `patchelf` using checked-in CMake script + templates rather than large embedded `install(CODE ...)` strings. + +Release wheels are built by `.github/workflows/release.yml` with cibuildwheel +and are smoke-tested by importing the `roboplan` namespace and every compiled +submodule before publishing through PyPI trusted publishing. diff --git a/packaging/python/cmake/repair_cmeel_rpaths.cmake.in b/packaging/python/cmake/repair_cmeel_rpaths.cmake.in new file mode 100644 index 00000000..8ca69492 --- /dev/null +++ b/packaging/python/cmake/repair_cmeel_rpaths.cmake.in @@ -0,0 +1,49 @@ +function(_roboplan_cmeel_patch_rpath file rpath) + set(_patch_file "${file}") + if(IS_SYMLINK "${_patch_file}") + file(READ_SYMLINK "${_patch_file}" _symlink_target) + if(IS_ABSOLUTE "${_symlink_target}") + return() + endif() + get_filename_component(_symlink_dir "${_patch_file}" DIRECTORY) + set(_patch_file "${_symlink_dir}/${_symlink_target}") + endif() + + if(EXISTS "${_patch_file}") + execute_process( + COMMAND chmod u+w "${_patch_file}" + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND "@ROBOPLAN_CMEEL_PATCHELF@" --set-rpath "${rpath}" "${_patch_file}" + COMMAND_ERROR_IS_FATAL ANY + ) + endif() +endfunction() + +file(GLOB_RECURSE _roboplan_cmeel_installed_shared_objects + "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/*.so" + "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/*.so.*" +) + +set(_roboplan_cmeel_extensions) +set(_roboplan_cmeel_libraries) +foreach(_file IN LISTS _roboplan_cmeel_installed_shared_objects) + if(_file MATCHES "/site-packages/roboplan/.+\\.so$") + list(APPEND _roboplan_cmeel_extensions "${_file}") + elseif(_file MATCHES "/lib/lib(filters|roboplan.*|OsqpEigen|osqp|qdldl|toppra)\\.so(\\..*)?$") + list(APPEND _roboplan_cmeel_libraries "${_file}") + endif() +endforeach() + +if(BUILD_STANDALONE_PYTHON_BINDINGS AND NOT _roboplan_cmeel_extensions) + message(FATAL_ERROR "ROBOPLAN_CMEEL RPATH repair found no Python extensions") +endif() + +foreach(_file IN LISTS _roboplan_cmeel_extensions) + _roboplan_cmeel_patch_rpath("${_file}" "$ORIGIN/../../../..") +endforeach() + +foreach(_file IN LISTS _roboplan_cmeel_libraries) + _roboplan_cmeel_patch_rpath("${_file}" "$ORIGIN") +endforeach() diff --git a/packaging/python/cmake/repair_unified_rpaths.cmake.in b/packaging/python/cmake/repair_unified_rpaths.cmake.in new file mode 100644 index 00000000..dde72766 --- /dev/null +++ b/packaging/python/cmake/repair_unified_rpaths.cmake.in @@ -0,0 +1,35 @@ +function(_roboplan_patch_rpath file rpath) + set(_patch_file "${file}") + if(IS_SYMLINK "${_patch_file}") + file(READ_SYMLINK "${_patch_file}" _symlink_target) + if(IS_ABSOLUTE "${_symlink_target}") + return() + endif() + get_filename_component(_symlink_dir "${_patch_file}" DIRECTORY) + set(_patch_file "${_symlink_dir}/${_symlink_target}") + endif() + + if(EXISTS "${_patch_file}") + execute_process( + COMMAND chmod u+w "${_patch_file}" + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND "@ROBOPLAN_UNIFIED_PATCHELF@" --set-rpath "${rpath}" "${_patch_file}" + COMMAND_ERROR_IS_FATAL ANY + ) + endif() +endfunction() + +file(GLOB_RECURSE _roboplan_extensions "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/roboplan/*.so") +foreach(_file IN LISTS _roboplan_extensions) + _roboplan_patch_rpath("${_file}" "$ORIGIN/../../lib:$ORIGIN/../../cmeel.prefix/lib") +endforeach() + +file(GLOB _roboplan_libraries + "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libfilters.so*" + "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libroboplan*.so*" +) +foreach(_file IN LISTS _roboplan_libraries) + _roboplan_patch_rpath("${_file}" "$ORIGIN:$ORIGIN/../cmeel.prefix/lib") +endforeach() diff --git a/packaging/python/cmake/roboplan_python_packaging.cmake b/packaging/python/cmake/roboplan_python_packaging.cmake new file mode 100644 index 00000000..92013beb --- /dev/null +++ b/packaging/python/cmake/roboplan_python_packaging.cmake @@ -0,0 +1,190 @@ +function(roboplan_configure_scikit_build_prefix) + find_package(Python COMPONENTS Interpreter REQUIRED) + execute_process( + COMMAND + "${Python_EXECUTABLE}" + -c + "import pathlib, sysconfig; prefix = pathlib.Path(sysconfig.get_path('purelib')) / 'cmeel.prefix'; print(prefix if prefix.exists() else '')" + OUTPUT_VARIABLE ROBOPLAN_CMEEL_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(ROBOPLAN_CMEEL_PREFIX "${ROBOPLAN_CMEEL_PREFIX}" PARENT_SCOPE) + if(ROBOPLAN_CMEEL_PREFIX) + list(PREPEND CMAKE_PREFIX_PATH "${ROBOPLAN_CMEEL_PREFIX}") + set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) + endif() +endfunction() + +function(roboplan_ensure_hpp_fcl_target) + find_package(hpp-fcl CONFIG QUIET) + if(TARGET hpp-fcl::hpp-fcl) + return() + endif() + + find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR + NAMES hpp/fcl/fwd.hh + HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" + ) + find_library(ROBOPLAN_HPP_FCL_LIBRARY + NAMES hpp-fcl + HINTS + "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "/opt/ros/$ENV{ROS_DISTRO}/lib" + ) + if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) + add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) + set_target_properties(hpp-fcl::hpp-fcl PROPERTIES + IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" + ) + endif() +endfunction() + +function(roboplan_configure_unified_python_wheel) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_program(ROBOPLAN_UNIFIED_PATCHELF patchelf REQUIRED) + endif() + + foreach(target IN ITEMS + roboplan_example_models + roboplan + filters + roboplan_simple_ik + roboplan_oink + roboplan_rrt + roboplan_toppra) + if(TARGET ${target}) + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN") + endif() + endforeach() + + foreach(target IN ITEMS + _example_models_ext + _core_ext + _filters_ext + _simple_ik_ext + _optimal_ik_ext + _rrt_ext + _toppra_ext) + if(TARGET ${target}) + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN/../../lib") + endif() + endforeach() + + foreach(library IN ITEMS + OsqpEigen + boost_atomic + boost_filesystem + boost_serialization + boost_system + coal + console_bridge + gz-math + gz-utils + octomap + octomath + osqp + pinocchio_collision + pinocchio_default + pinocchio_extra + pinocchio_parsers + qdldl + qhull_r + sdformat + tinyxml2 + toppra + urdfdom_model + urdfdom_sensor + urdfdom_world + yaml-cpp) + find_library(ROBOPLAN_UNIFIED_${library}_LIBRARY NAMES ${library}) + if(ROBOPLAN_UNIFIED_${library}_LIBRARY) + install(FILES "${ROBOPLAN_UNIFIED_${library}_LIBRARY}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_UNIFIED_${library}_LIBRARY}" ROBOPLAN_UNIFIED_${library}_REAL_LIBRARY) + install(FILES "${ROBOPLAN_UNIFIED_${library}_REAL_LIBRARY}" DESTINATION lib) + endif() + endforeach() + + foreach(library IN ITEMS + libassimp.so.6 + libboost_atomic.so.1.90.0 + libboost_filesystem.so.1.88.0 + libboost_filesystem.so.1.90.0 + libboost_serialization.so.1.88.0 + libboost_serialization.so.1.90.0 + libboost_system.so.1.88.0 + libconsole_bridge.so.1.0 + libgz-math.so.9 + libgz-utils.so.4 + liboctomap.so.1.10 + liboctomath.so.1.10 + libqhull_r.so.8.0 + libsdformat.so.16 + libtinyxml2.so.11 + liburdfdom_model.so.6 + liburdfdom_sensor.so.6 + liburdfdom_world.so.6 + libyaml-cpp.so.0.8) + find_file(ROBOPLAN_UNIFIED_${library}_FILE NAMES ${library} + PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" "${ROBOPLAN_CMEEL_PREFIX}/lib" + NO_DEFAULT_PATH) + if(ROBOPLAN_UNIFIED_${library}_FILE) + install(FILES "${ROBOPLAN_UNIFIED_${library}_FILE}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_UNIFIED_${library}_FILE}" ROBOPLAN_UNIFIED_${library}_REAL_FILE) + install(FILES "${ROBOPLAN_UNIFIED_${library}_REAL_FILE}" DESTINATION lib) + endif() + endforeach() + + find_library(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY NAMES yaml-cpp) + if(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY) + install(FILES "${ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY}" DESTINATION lib RENAME libyaml-cpp.so.0.8) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + configure_file( + "${PROJECT_SOURCE_DIR}/packaging/python/cmake/repair_unified_rpaths.cmake.in" + "${PROJECT_BINARY_DIR}/roboplan_repair_unified_rpaths.cmake" + @ONLY + ) + install(SCRIPT "${PROJECT_BINARY_DIR}/roboplan_repair_unified_rpaths.cmake") + endif() +endfunction() + +function(roboplan_configure_cmeel_package) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + return() + endif() + + find_program(ROBOPLAN_CMEEL_PATCHELF patchelf REQUIRED) + foreach(library IN ITEMS + OsqpEigen + osqp + qdldl + toppra) + find_library(ROBOPLAN_CMEEL_${library}_LIBRARY NAMES ${library}) + if(ROBOPLAN_CMEEL_${library}_LIBRARY) + install(FILES "${ROBOPLAN_CMEEL_${library}_LIBRARY}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_LIBRARY}" ROBOPLAN_CMEEL_${library}_REAL_LIBRARY) + install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_LIBRARY}" DESTINATION lib) + endif() + endforeach() + + foreach(library IN ITEMS + libOsqpEigen.so.0.11.0) + find_file(ROBOPLAN_CMEEL_${library}_FILE NAMES ${library} + PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" + NO_DEFAULT_PATH) + if(ROBOPLAN_CMEEL_${library}_FILE) + install(FILES "${ROBOPLAN_CMEEL_${library}_FILE}" DESTINATION lib) + file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_FILE}" ROBOPLAN_CMEEL_${library}_REAL_FILE) + install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_FILE}" DESTINATION lib) + endif() + endforeach() + + configure_file( + "${PROJECT_SOURCE_DIR}/packaging/python/cmake/repair_cmeel_rpaths.cmake.in" + "${PROJECT_BINARY_DIR}/roboplan_repair_cmeel_rpaths.cmake" + @ONLY + ) + install(SCRIPT "${PROJECT_BINARY_DIR}/roboplan_repair_cmeel_rpaths.cmake") +endfunction() From 327a5e2d6ba079046b1a2d4f5f98a36f1b8bfc74 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:05:19 -0700 Subject: [PATCH 19/52] Route root CMake through packaging helpers Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- CMakeLists.txt | 169 +------------------ cmake/roboplan_cmeel_repair/CMakeLists.txt | 51 ------ cmake/roboplan_unified_repair/CMakeLists.txt | 37 ---- 3 files changed, 7 insertions(+), 250 deletions(-) delete mode 100644 cmake/roboplan_cmeel_repair/CMakeLists.txt delete mode 100644 cmake/roboplan_unified_repair/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b2b1feb..04ce15d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,41 +8,13 @@ option( OFF ) +include(packaging/python/cmake/roboplan_python_packaging.cmake) + if(SKBUILD) - find_package(Python COMPONENTS Interpreter REQUIRED) - execute_process( - COMMAND - "${Python_EXECUTABLE}" - -c - "import pathlib, sysconfig; prefix = pathlib.Path(sysconfig.get_path('purelib')) / 'cmeel.prefix'; print(prefix if prefix.exists() else '')" - OUTPUT_VARIABLE ROBOPLAN_CMEEL_PREFIX - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - if(ROBOPLAN_CMEEL_PREFIX) - list(PREPEND CMAKE_PREFIX_PATH "${ROBOPLAN_CMEEL_PREFIX}") - endif() + roboplan_configure_scikit_build_prefix() endif() -find_package(hpp-fcl CONFIG QUIET) -if(NOT TARGET hpp-fcl::hpp-fcl) - find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR - NAMES hpp/fcl/fwd.hh - HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" - ) - find_library(ROBOPLAN_HPP_FCL_LIBRARY - NAMES hpp-fcl - HINTS - "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" - "/opt/ros/$ENV{ROS_DISTRO}/lib" - ) - if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) - add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) - set_target_properties(hpp-fcl::hpp-fcl PROPERTIES - IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" - ) - endif() -endif() +roboplan_ensure_hpp_fcl_target() if(BUILD_STANDALONE_PYTHON_BINDINGS) find_package(roboplan_example_models REQUIRED CONFIG) @@ -90,136 +62,9 @@ else() endif() if(SKBUILD) - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_program(ROBOPLAN_UNIFIED_PATCHELF patchelf REQUIRED) - endif() - - foreach(target IN ITEMS - roboplan_example_models - roboplan - filters - roboplan_simple_ik - roboplan_oink - roboplan_rrt - roboplan_toppra) - if(TARGET ${target}) - set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN") - endif() - endforeach() - - foreach(target IN ITEMS - _example_models_ext - _core_ext - _filters_ext - _simple_ik_ext - _optimal_ik_ext - _rrt_ext - _toppra_ext) - if(TARGET ${target}) - set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN/../../lib") - endif() - endforeach() - - foreach(library IN ITEMS - OsqpEigen - boost_atomic - boost_filesystem - boost_serialization - boost_system - coal - console_bridge - gz-math - gz-utils - octomap - octomath - osqp - pinocchio_collision - pinocchio_default - pinocchio_extra - pinocchio_parsers - qdldl - qhull_r - sdformat - tinyxml2 - toppra - urdfdom_model - urdfdom_sensor - urdfdom_world - yaml-cpp) - find_library(ROBOPLAN_UNIFIED_${library}_LIBRARY NAMES ${library}) - if(ROBOPLAN_UNIFIED_${library}_LIBRARY) - install(FILES "${ROBOPLAN_UNIFIED_${library}_LIBRARY}" DESTINATION lib) - file(REAL_PATH "${ROBOPLAN_UNIFIED_${library}_LIBRARY}" ROBOPLAN_UNIFIED_${library}_REAL_LIBRARY) - install(FILES "${ROBOPLAN_UNIFIED_${library}_REAL_LIBRARY}" DESTINATION lib) - endif() - endforeach() - - foreach(library IN ITEMS - libassimp.so.6 - libboost_atomic.so.1.90.0 - libboost_filesystem.so.1.88.0 - libboost_filesystem.so.1.90.0 - libboost_serialization.so.1.88.0 - libboost_serialization.so.1.90.0 - libboost_system.so.1.88.0 - libconsole_bridge.so.1.0 - libgz-math.so.9 - libgz-utils.so.4 - liboctomap.so.1.10 - liboctomath.so.1.10 - libqhull_r.so.8.0 - libsdformat.so.16 - libtinyxml2.so.11 - liburdfdom_model.so.6 - liburdfdom_sensor.so.6 - liburdfdom_world.so.6 - libyaml-cpp.so.0.8) - find_file(ROBOPLAN_UNIFIED_${library}_FILE NAMES ${library} - PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" "${ROBOPLAN_CMEEL_PREFIX}/lib" - NO_DEFAULT_PATH) - if(ROBOPLAN_UNIFIED_${library}_FILE) - install(FILES "${ROBOPLAN_UNIFIED_${library}_FILE}" DESTINATION lib) - file(REAL_PATH "${ROBOPLAN_UNIFIED_${library}_FILE}" ROBOPLAN_UNIFIED_${library}_REAL_FILE) - install(FILES "${ROBOPLAN_UNIFIED_${library}_REAL_FILE}" DESTINATION lib) - endif() - endforeach() - - find_library(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY NAMES yaml-cpp) - if(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY) - install(FILES "${ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY}" DESTINATION lib RENAME libyaml-cpp.so.0.8) - endif() - - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - add_subdirectory(cmake/roboplan_unified_repair) - endif() + roboplan_configure_unified_python_wheel() endif() -if(ROBOPLAN_CMEEL AND CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_program(ROBOPLAN_CMEEL_PATCHELF patchelf REQUIRED) - foreach(library IN ITEMS - OsqpEigen - osqp - qdldl - toppra) - find_library(ROBOPLAN_CMEEL_${library}_LIBRARY NAMES ${library}) - if(ROBOPLAN_CMEEL_${library}_LIBRARY) - install(FILES "${ROBOPLAN_CMEEL_${library}_LIBRARY}" DESTINATION lib) - file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_LIBRARY}" ROBOPLAN_CMEEL_${library}_REAL_LIBRARY) - install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_LIBRARY}" DESTINATION lib) - endif() - endforeach() - - foreach(library IN ITEMS - libOsqpEigen.so.0.11.0) - find_file(ROBOPLAN_CMEEL_${library}_FILE NAMES ${library} - PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" - NO_DEFAULT_PATH) - if(ROBOPLAN_CMEEL_${library}_FILE) - install(FILES "${ROBOPLAN_CMEEL_${library}_FILE}" DESTINATION lib) - file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_FILE}" ROBOPLAN_CMEEL_${library}_REAL_FILE) - install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_FILE}" DESTINATION lib) - endif() - endforeach() - - add_subdirectory(cmake/roboplan_cmeel_repair) +if(ROBOPLAN_CMEEL) + roboplan_configure_cmeel_package() endif() diff --git a/cmake/roboplan_cmeel_repair/CMakeLists.txt b/cmake/roboplan_cmeel_repair/CMakeLists.txt deleted file mode 100644 index a26317cc..00000000 --- a/cmake/roboplan_cmeel_repair/CMakeLists.txt +++ /dev/null @@ -1,51 +0,0 @@ -install(CODE " - function(_roboplan_cmeel_patch_rpath file rpath) - set(_patch_file \"\${file}\") - if(IS_SYMLINK \"\${_patch_file}\") - file(READ_SYMLINK \"\${_patch_file}\" _symlink_target) - if(IS_ABSOLUTE \"\${_symlink_target}\") - return() - endif() - get_filename_component(_symlink_dir \"\${_patch_file}\" DIRECTORY) - set(_patch_file \"\${_symlink_dir}/\${_symlink_target}\") - endif() - - if(EXISTS \"\${_patch_file}\") - execute_process( - COMMAND chmod u+w \"\${_patch_file}\" - COMMAND_ERROR_IS_FATAL ANY - ) - execute_process( - COMMAND \"${ROBOPLAN_CMEEL_PATCHELF}\" --set-rpath \"\${rpath}\" \"\${_patch_file}\" - COMMAND_ERROR_IS_FATAL ANY - ) - endif() - endfunction() - - file(GLOB_RECURSE _roboplan_cmeel_installed_shared_objects - \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/*.so\" - \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/*.so.*\" - ) - - set(_roboplan_cmeel_extensions) - set(_roboplan_cmeel_libraries) - foreach(_file IN LISTS _roboplan_cmeel_installed_shared_objects) - if(_file MATCHES \"/site-packages/roboplan/.+\\\\.so$\") - list(APPEND _roboplan_cmeel_extensions \"\${_file}\") - elseif(_file MATCHES \"/lib/lib(filters|roboplan.*|OsqpEigen|osqp|qdldl|toppra)\\\\.so(\\\\..*)?$\") - list(APPEND _roboplan_cmeel_libraries \"\${_file}\") - endif() - endforeach() - - if(BUILD_STANDALONE_PYTHON_BINDINGS AND NOT _roboplan_cmeel_extensions) - message(FATAL_ERROR \"ROBOPLAN_CMEEL RPATH repair found no Python extensions\") - endif() - - foreach(_file IN LISTS _roboplan_cmeel_extensions) - _roboplan_cmeel_patch_rpath(\"\${_file}\" \"\\\$ORIGIN/../../../..\") - endforeach() - - foreach(_file IN LISTS _roboplan_cmeel_libraries) - _roboplan_cmeel_patch_rpath(\"\${_file}\" \"\\\$ORIGIN\") - endforeach() -") diff --git a/cmake/roboplan_unified_repair/CMakeLists.txt b/cmake/roboplan_unified_repair/CMakeLists.txt deleted file mode 100644 index 9d4cf06a..00000000 --- a/cmake/roboplan_unified_repair/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -install(CODE " - function(_roboplan_patch_rpath file rpath) - set(_patch_file \"\${file}\") - if(IS_SYMLINK \"\${_patch_file}\") - file(READ_SYMLINK \"\${_patch_file}\" _symlink_target) - if(IS_ABSOLUTE \"\${_symlink_target}\") - return() - endif() - get_filename_component(_symlink_dir \"\${_patch_file}\" DIRECTORY) - set(_patch_file \"\${_symlink_dir}/\${_symlink_target}\") - endif() - - if(EXISTS \"\${_patch_file}\") - execute_process( - COMMAND chmod u+w \"\${_patch_file}\" - COMMAND_ERROR_IS_FATAL ANY - ) - execute_process( - COMMAND \"${ROBOPLAN_UNIFIED_PATCHELF}\" --set-rpath \"\${rpath}\" \"\${_patch_file}\" - COMMAND_ERROR_IS_FATAL ANY - ) - endif() - endfunction() - - file(GLOB_RECURSE _roboplan_extensions \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/roboplan/*.so\") - foreach(_file IN LISTS _roboplan_extensions) - _roboplan_patch_rpath(\"\${_file}\" \"\\\$ORIGIN/../../lib:\\\$ORIGIN/../../cmeel.prefix/lib\") - endforeach() - - file(GLOB _roboplan_libraries - \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/lib/libfilters.so*\" - \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/lib/libroboplan*.so*\" - ) - foreach(_file IN LISTS _roboplan_libraries) - _roboplan_patch_rpath(\"\${_file}\" \"\\\$ORIGIN:\\\$ORIGIN/../cmeel.prefix/lib\") - endforeach() -") From 296be81e36fc3deb0a51755dc13a7a0018ba1c33 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:05:42 -0700 Subject: [PATCH 20/52] Update packaging contract checks Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .github/workflows/release.yml | 4 +-- tests/unified_python_package_test.py | 40 +++++++++++++++++----------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d47bf620..0001a3eb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,7 +110,7 @@ jobs: MAKEFLAGS="-j2" NINJAFLAGS="-j2" CIBW_TEST_COMMAND: >- - python -c "import roboplan.core; import roboplan.filters; import roboplan.example_models; + python -c "import roboplan; import roboplan.core; import roboplan.filters; import roboplan.example_models; import roboplan.simple_ik; import roboplan.optimal_ik; import roboplan.rrt; import roboplan.toppra; print('roboplan-dimos imports ok')" - name: Upload wheels @@ -149,7 +149,6 @@ jobs: with: packages-dir: dist/ repository-url: https://test.pypi.org/legacy/ - skip-existing: true publish_pypi: name: Publish to PyPI @@ -179,3 +178,4 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ + skip-existing: true diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 047b3721..cc8b2208 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -54,15 +54,19 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: def test_core_cmake_finds_pinocchio_transitive_targets_explicitly() -> None: root = _read("CMakeLists.txt") + helper = _read("packaging/python/cmake/roboplan_python_packaging.cmake") source = _read("roboplan/CMakeLists.txt") config = _read("roboplan/cmake/roboplanConfig.cmake.in") - assert "find_package(hpp-fcl CONFIG QUIET)" in root - assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in root - assert root.index("find_package(hpp-fcl CONFIG QUIET)") < root.index( + assert "include(packaging/python/cmake/roboplan_python_packaging.cmake)" in root + assert "roboplan_ensure_hpp_fcl_target()" in root + assert root.index("roboplan_ensure_hpp_fcl_target()") < root.index( "add_subdirectory(roboplan)" ) + assert "find_package(hpp-fcl CONFIG QUIET)" in helper + assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in helper + assert "find_package(pinocchio REQUIRED)" in source assert "find_package(hpp-fcl CONFIG QUIET)" in source assert "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" in source @@ -128,21 +132,25 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No ), "root CMakeLists.txt is required for the unified roboplan wheel" source = cmake.read_text(encoding="utf-8") + helper_source = _read("packaging/python/cmake/roboplan_python_packaging.cmake") package_order = re.findall(r"add_subdirectory\((roboplan(?:_[a-z_]+)?)\)", source) - repair_source = _read("cmake/roboplan_unified_repair/CMakeLists.txt") - - assert "add_subdirectory(cmake/roboplan_unified_repair)" in source - assert "cmeel.prefix" in source - assert "list(PREPEND CMAKE_PREFIX_PATH" in source - assert "${ROBOPLAN_CMEEL_PREFIX}/lib" in source - assert "RENAME libyaml-cpp.so.0.8" in source - assert "boost_atomic" in source - assert "libboost_atomic.so.1.90.0" in source - assert "libboost_filesystem.so.1.90.0" in source - assert "liburdfdom_world.so.6" in source + repair_source = _read("packaging/python/cmake/repair_unified_rpaths.cmake.in") + + assert "roboplan_configure_scikit_build_prefix()" in source + assert "roboplan_configure_unified_python_wheel()" in source + assert "cmeel.prefix" in helper_source + assert "list(PREPEND CMAKE_PREFIX_PATH" in helper_source + assert "${ROBOPLAN_CMEEL_PREFIX}/lib" in helper_source + assert "RENAME libyaml-cpp.so.0.8" in helper_source + assert "boost_atomic" in helper_source + assert "libboost_atomic.so.1.90.0" in helper_source + assert "libboost_filesystem.so.1.90.0" in helper_source + assert "liburdfdom_world.so.6" in helper_source + assert "install(SCRIPT" in helper_source + assert "install(CODE" not in helper_source assert "--set-rpath" in repair_source - assert "$ORIGIN/../../lib" in source + assert "$ORIGIN/../../lib" in helper_source assert package_order == [ "roboplan_example_models", "roboplan", @@ -215,7 +223,7 @@ def test_release_workflow_builds_repaired_wheels_and_uses_trusted_publishing() - assert "CMAKE_BUILD_PARALLEL_LEVEL=2" in workflow assert 'MAKEFLAGS="-j2"' in workflow assert 'NINJAFLAGS="-j2"' in workflow - assert "roboplan.core" in workflow + assert "import roboplan; import roboplan.core" in workflow assert "roboplan.toppra" in workflow assert "pypa/gh-action-pypi-publish@release/v1" in workflow assert "id-token: write" in workflow From d5761fc3ba9ab0beab010f02293f5cd799b7e34d Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:53:44 -0700 Subject: [PATCH 21/52] Add packaging-only CMake entrypoint Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/python/CMakeLists.txt | 74 +++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 75 insertions(+) create mode 100644 packaging/python/CMakeLists.txt diff --git a/packaging/python/CMakeLists.txt b/packaging/python/CMakeLists.txt new file mode 100644 index 00000000..3ab9dc49 --- /dev/null +++ b/packaging/python/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 3.22) +project(roboplan VERSION 0.4.0) + +get_filename_component(ROBOPLAN_REPOSITORY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE) + +option(ROBOPLAN_CMEEL "Build RoboPlan with cmeel packaging conventions" OFF) +option( + BUILD_STANDALONE_PYTHON_BINDINGS + "Build only Python bindings against pre-installed RoboPlan native packages" + OFF +) + +include(cmake/roboplan_python_packaging.cmake) + +if(SKBUILD) + roboplan_configure_scikit_build_prefix() +endif() + +roboplan_ensure_hpp_fcl_target() + +if(BUILD_STANDALONE_PYTHON_BINDINGS) + find_package(roboplan_example_models REQUIRED CONFIG) + find_package(roboplan REQUIRED CONFIG) + find_package(roboplan_simple_ik REQUIRED CONFIG) + find_package(roboplan_oink REQUIRED CONFIG) + find_package(roboplan_rrt REQUIRED CONFIG) + find_package(roboplan_toppra REQUIRED CONFIG) + + if(TARGET roboplan_example_models::roboplan_example_models AND NOT TARGET roboplan_example_models) + add_library(roboplan_example_models ALIAS roboplan_example_models::roboplan_example_models) + endif() + if(TARGET roboplan::roboplan AND NOT TARGET roboplan) + add_library(roboplan ALIAS roboplan::roboplan) + endif() + if(TARGET roboplan::filters AND NOT TARGET filters) + add_library(filters ALIAS roboplan::filters) + endif() + if(TARGET roboplan_simple_ik::roboplan_simple_ik AND NOT TARGET roboplan_simple_ik) + add_library(roboplan_simple_ik ALIAS roboplan_simple_ik::roboplan_simple_ik) + endif() + if(TARGET roboplan_oink::roboplan_oink AND NOT TARGET roboplan_oink) + add_library(roboplan_oink ALIAS roboplan_oink::roboplan_oink) + endif() + if(TARGET roboplan_rrt::roboplan_rrt AND NOT TARGET roboplan_rrt) + add_library(roboplan_rrt ALIAS roboplan_rrt::roboplan_rrt) + endif() + if(TARGET roboplan_toppra::roboplan_toppra AND NOT TARGET roboplan_toppra) + add_library(roboplan_toppra ALIAS roboplan_toppra::roboplan_toppra) + endif() + + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_example_models/bindings" "bindings/roboplan_example_models") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan/bindings" "bindings/roboplan") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_simple_ik/bindings" "bindings/roboplan_simple_ik") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_oink/bindings" "bindings/roboplan_oink") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_rrt/bindings" "bindings/roboplan_rrt") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_toppra/bindings" "bindings/roboplan_toppra") +else() + roboplan_register_build_tree_packages() + + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_example_models" "roboplan_example_models") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan" "roboplan") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_simple_ik" "roboplan_simple_ik") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_oink" "roboplan_oink") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_rrt" "roboplan_rrt") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_toppra" "roboplan_toppra") +endif() + +if(SKBUILD) + roboplan_configure_unified_python_wheel() +endif() + +if(ROBOPLAN_CMEEL) + roboplan_configure_cmeel_package() +endif() diff --git a/pyproject.toml b/pyproject.toml index bd5e634a..6298206e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ Homepage = "https://github.com/sea-bass/roboplan" [tool.scikit-build] minimum-version = "build-system.requires" build-dir = "build/{wheel_tag}" +cmake.source-dir = "packaging/python" cmake.define.BUILD_PYTHON_BINDINGS = "ON" cmake.define.BUILD_TESTING = "OFF" cmake.define.GENERATE_PYTHON_STUBS = "OFF" From fce21c11105fbca0633eae2986fb7f2aeed9e9ee Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:54:02 -0700 Subject: [PATCH 22/52] Point cmeel manifests at packaging entrypoint Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/cmeel/libroboplan/pyproject.toml | 2 +- packaging/cmeel/roboplan/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/cmeel/libroboplan/pyproject.toml b/packaging/cmeel/libroboplan/pyproject.toml index aede67f7..4ba652ad 100644 --- a/packaging/cmeel/libroboplan/pyproject.toml +++ b/packaging/cmeel/libroboplan/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ Homepage = "https://github.com/sea-bass/roboplan" [tool.cmeel] -source = "../../.." +source = "../../python" has-sitelib = false run-tests = false configure-args = [ diff --git a/packaging/cmeel/roboplan/pyproject.toml b/packaging/cmeel/roboplan/pyproject.toml index 7354a446..8e0ccae1 100644 --- a/packaging/cmeel/roboplan/pyproject.toml +++ b/packaging/cmeel/roboplan/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ Homepage = "https://github.com/sea-bass/roboplan" [tool.cmeel] -source = "../../.." +source = "../../python" run-tests = false configure-args = [ "-DROBOPLAN_CMEEL=ON", From 3ff51f0f1b276f454394b0d1e3c7d7b1b6747b96 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:54:18 -0700 Subject: [PATCH 23/52] Centralize build tree package configs Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../cmake/roboplan_python_packaging.cmake | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/packaging/python/cmake/roboplan_python_packaging.cmake b/packaging/python/cmake/roboplan_python_packaging.cmake index 92013beb..b9c2d5e3 100644 --- a/packaging/python/cmake/roboplan_python_packaging.cmake +++ b/packaging/python/cmake/roboplan_python_packaging.cmake @@ -40,6 +40,46 @@ function(roboplan_ensure_hpp_fcl_target) endif() endfunction() +function(roboplan_register_build_tree_package package_name) + set(options) + set(one_value_args) + set(multi_value_args ALIASES) + cmake_parse_arguments(ARG "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + set(_package_dir "${PROJECT_BINARY_DIR}/packaging-python-package-configs/${package_name}") + file(MAKE_DIRECTORY "${_package_dir}") + set(_config_file "${_package_dir}/${package_name}Config.cmake") + file(WRITE "${_config_file}" "# Generated for the packaging/python build tree.\n") + + foreach(alias_pair IN LISTS ARG_ALIASES) + string(REPLACE "=" ";" alias_parts "${alias_pair}") + list(GET alias_parts 0 namespaced_target) + list(GET alias_parts 1 local_target) + file(APPEND "${_config_file}" + "if(TARGET ${local_target} AND NOT TARGET ${namespaced_target})\n" + " add_library(${namespaced_target} ALIAS ${local_target})\n" + "endif()\n" + ) + endforeach() + + set(${package_name}_DIR "${_package_dir}" CACHE PATH "Build-tree ${package_name} package config" FORCE) +endfunction() + +function(roboplan_register_build_tree_packages) + roboplan_register_build_tree_package(roboplan_example_models + ALIASES roboplan_example_models::roboplan_example_models=roboplan_example_models) + roboplan_register_build_tree_package(roboplan + ALIASES roboplan::roboplan=roboplan roboplan::filters=filters) + roboplan_register_build_tree_package(roboplan_simple_ik + ALIASES roboplan_simple_ik::roboplan_simple_ik=roboplan_simple_ik) + roboplan_register_build_tree_package(roboplan_oink + ALIASES roboplan_oink::roboplan_oink=roboplan_oink) + roboplan_register_build_tree_package(roboplan_rrt + ALIASES roboplan_rrt::roboplan_rrt=roboplan_rrt) + roboplan_register_build_tree_package(roboplan_toppra + ALIASES roboplan_toppra::roboplan_toppra=roboplan_toppra) +endfunction() + function(roboplan_configure_unified_python_wheel) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_program(ROBOPLAN_UNIFIED_PATCHELF patchelf REQUIRED) @@ -142,7 +182,7 @@ function(roboplan_configure_unified_python_wheel) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") configure_file( - "${PROJECT_SOURCE_DIR}/packaging/python/cmake/repair_unified_rpaths.cmake.in" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/repair_unified_rpaths.cmake.in" "${PROJECT_BINARY_DIR}/roboplan_repair_unified_rpaths.cmake" @ONLY ) @@ -182,7 +222,7 @@ function(roboplan_configure_cmeel_package) endforeach() configure_file( - "${PROJECT_SOURCE_DIR}/packaging/python/cmake/repair_cmeel_rpaths.cmake.in" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/repair_cmeel_rpaths.cmake.in" "${PROJECT_BINARY_DIR}/roboplan_repair_cmeel_rpaths.cmake" @ONLY ) From 13030d28a296dbe686bbd14d339dccf3e016e311 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:54:35 -0700 Subject: [PATCH 24/52] Remove root packaging superbuild Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- CMakeLists.txt | 70 -------------------------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 04ce15d8..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,70 +0,0 @@ -cmake_minimum_required(VERSION 3.22) -project(roboplan VERSION 0.4.0) - -option(ROBOPLAN_CMEEL "Build RoboPlan with cmeel packaging conventions" OFF) -option( - BUILD_STANDALONE_PYTHON_BINDINGS - "Build only Python bindings against pre-installed RoboPlan native packages" - OFF -) - -include(packaging/python/cmake/roboplan_python_packaging.cmake) - -if(SKBUILD) - roboplan_configure_scikit_build_prefix() -endif() - -roboplan_ensure_hpp_fcl_target() - -if(BUILD_STANDALONE_PYTHON_BINDINGS) - find_package(roboplan_example_models REQUIRED CONFIG) - find_package(roboplan REQUIRED CONFIG) - find_package(roboplan_simple_ik REQUIRED CONFIG) - find_package(roboplan_oink REQUIRED CONFIG) - find_package(roboplan_rrt REQUIRED CONFIG) - find_package(roboplan_toppra REQUIRED CONFIG) - - if(TARGET roboplan_example_models::roboplan_example_models AND NOT TARGET roboplan_example_models) - add_library(roboplan_example_models ALIAS roboplan_example_models::roboplan_example_models) - endif() - if(TARGET roboplan::roboplan AND NOT TARGET roboplan) - add_library(roboplan ALIAS roboplan::roboplan) - endif() - if(TARGET roboplan::filters AND NOT TARGET filters) - add_library(filters ALIAS roboplan::filters) - endif() - if(TARGET roboplan_simple_ik::roboplan_simple_ik AND NOT TARGET roboplan_simple_ik) - add_library(roboplan_simple_ik ALIAS roboplan_simple_ik::roboplan_simple_ik) - endif() - if(TARGET roboplan_oink::roboplan_oink AND NOT TARGET roboplan_oink) - add_library(roboplan_oink ALIAS roboplan_oink::roboplan_oink) - endif() - if(TARGET roboplan_rrt::roboplan_rrt AND NOT TARGET roboplan_rrt) - add_library(roboplan_rrt ALIAS roboplan_rrt::roboplan_rrt) - endif() - if(TARGET roboplan_toppra::roboplan_toppra AND NOT TARGET roboplan_toppra) - add_library(roboplan_toppra ALIAS roboplan_toppra::roboplan_toppra) - endif() - - add_subdirectory(roboplan_example_models/bindings) - add_subdirectory(roboplan/bindings) - add_subdirectory(roboplan_simple_ik/bindings) - add_subdirectory(roboplan_oink/bindings) - add_subdirectory(roboplan_rrt/bindings) - add_subdirectory(roboplan_toppra/bindings) -else() - add_subdirectory(roboplan_example_models) - add_subdirectory(roboplan) - add_subdirectory(roboplan_simple_ik) - add_subdirectory(roboplan_oink) - add_subdirectory(roboplan_rrt) - add_subdirectory(roboplan_toppra) -endif() - -if(SKBUILD) - roboplan_configure_unified_python_wheel() -endif() - -if(ROBOPLAN_CMEEL) - roboplan_configure_cmeel_package() -endif() From 8cb87769f787ed5df4729aa3569416c1bc4d6d78 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:54:52 -0700 Subject: [PATCH 25/52] Restore core package CMake files Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan/CMakeLists.txt | 48 -------------------------- roboplan/cmake/roboplanConfig.cmake.in | 33 ------------------ roboplan_example_models/CMakeLists.txt | 6 ---- 3 files changed, 87 deletions(-) diff --git a/roboplan/CMakeLists.txt b/roboplan/CMakeLists.txt index edb2609c..d94aa251 100644 --- a/roboplan/CMakeLists.txt +++ b/roboplan/CMakeLists.txt @@ -8,39 +8,6 @@ endif() # Find dependencies. find_package(Eigen3 REQUIRED) find_package(pinocchio REQUIRED) -find_package(hpp-fcl CONFIG QUIET) -if(NOT TARGET hpp-fcl::hpp-fcl AND DEFINED pinocchio_DIR) - get_filename_component(ROBOPLAN_PINOCCHIO_CMAKE_DIR "${pinocchio_DIR}" DIRECTORY) - find_package(hpp-fcl CONFIG REQUIRED - PATHS "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" - NO_DEFAULT_PATH - ) -endif() -if(NOT TARGET hpp-fcl::hpp-fcl) - find_package(hpp-fcl CONFIG QUIET) -endif() -if(NOT TARGET hpp-fcl::hpp-fcl) - find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR - NAMES hpp/fcl/fwd.hh - HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" - ) - find_library(ROBOPLAN_HPP_FCL_LIBRARY - NAMES hpp-fcl - HINTS - "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" - "/opt/ros/$ENV{ROS_DISTRO}/lib" - ) - if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) - add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) - set_target_properties(hpp-fcl::hpp-fcl PROPERTIES - IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" - ) - endif() -endif() -if(NOT TARGET hpp-fcl::hpp-fcl) - message(FATAL_ERROR "Failed to resolve hpp-fcl::hpp-fcl") -endif() find_package(console_bridge REQUIRED) find_package(yaml-cpp REQUIRED) @@ -85,9 +52,6 @@ add_library(roboplan SHARED src/core/scene_utils.cpp src/core/types.cpp ) -if(NOT TARGET roboplan::roboplan) - add_library(roboplan::roboplan ALIAS roboplan) -endif() target_include_directories(roboplan PUBLIC $ $ @@ -105,12 +69,6 @@ else() target_link_libraries(roboplan PRIVATE yaml-cpp::yaml-cpp) endif() set_property(TARGET roboplan PROPERTY CXX_STANDARD 20) -if(SKBUILD OR ROBOPLAN_CMEEL) - set_target_properties(roboplan PROPERTIES - INSTALL_RPATH "$ORIGIN" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() add_library(filters SHARED src/filters/se3_low_pass_filter.cpp @@ -125,12 +83,6 @@ target_link_libraries(filters pinocchio::pinocchio ) set_property(TARGET filters PROPERTY CXX_STANDARD 20) -if(SKBUILD OR ROBOPLAN_CMEEL) - set_target_properties(filters PROPERTIES - INSTALL_RPATH "$ORIGIN" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() list(APPEND TARGETS_LIST filters) set(ROBOPLAN_TARGETS ${TARGETS_LIST}) diff --git a/roboplan/cmake/roboplanConfig.cmake.in b/roboplan/cmake/roboplanConfig.cmake.in index 2e900bdd..c6e7eead 100644 --- a/roboplan/cmake/roboplanConfig.cmake.in +++ b/roboplan/cmake/roboplanConfig.cmake.in @@ -2,39 +2,6 @@ find_package(Eigen3 REQUIRED) find_package(pinocchio REQUIRED) -find_package(hpp-fcl CONFIG QUIET) -if(NOT TARGET hpp-fcl::hpp-fcl AND DEFINED pinocchio_DIR) - get_filename_component(ROBOPLAN_PINOCCHIO_CMAKE_DIR "${pinocchio_DIR}" DIRECTORY) - find_package(hpp-fcl CONFIG REQUIRED - PATHS "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" - NO_DEFAULT_PATH - ) -endif() -if(NOT TARGET hpp-fcl::hpp-fcl) - find_package(hpp-fcl CONFIG QUIET) -endif() -if(NOT TARGET hpp-fcl::hpp-fcl) - find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR - NAMES hpp/fcl/fwd.hh - HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" - ) - find_library(ROBOPLAN_HPP_FCL_LIBRARY - NAMES hpp-fcl - HINTS - "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" - "/opt/ros/$ENV{ROS_DISTRO}/lib" - ) - if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) - add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) - set_target_properties(hpp-fcl::hpp-fcl PROPERTIES - IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" - ) - endif() -endif() -if(NOT TARGET hpp-fcl::hpp-fcl) - message(FATAL_ERROR "Failed to resolve hpp-fcl::hpp-fcl") -endif() find_package(tinyxml2_vendor QUIET) find_package(TinyXML2 QUIET) find_package(console_bridge QUIET) diff --git a/roboplan_example_models/CMakeLists.txt b/roboplan_example_models/CMakeLists.txt index 6167e39f..e1e00780 100644 --- a/roboplan_example_models/CMakeLists.txt +++ b/roboplan_example_models/CMakeLists.txt @@ -25,12 +25,6 @@ target_include_directories(roboplan_example_models PUBLIC $ ) set_property(TARGET roboplan_example_models PROPERTY CXX_STANDARD 20) -if(SKBUILD OR ROBOPLAN_CMEEL) - set_target_properties(roboplan_example_models PROPERTIES - INSTALL_RPATH "$ORIGIN" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() install(DIRECTORY include/ DESTINATION include) install( From a4aa6694f73bab2114d4d6108b37ac73f89ae644 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:55:17 -0700 Subject: [PATCH 26/52] Restore downstream native CMake files Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan_oink/CMakeLists.txt | 10 +--------- roboplan_rrt/CMakeLists.txt | 10 +--------- roboplan_simple_ik/CMakeLists.txt | 10 +--------- roboplan_toppra/CMakeLists.txt | 10 +--------- 4 files changed, 4 insertions(+), 36 deletions(-) diff --git a/roboplan_oink/CMakeLists.txt b/roboplan_oink/CMakeLists.txt index bc7cfb7a..82eadd60 100644 --- a/roboplan_oink/CMakeLists.txt +++ b/roboplan_oink/CMakeLists.txt @@ -13,9 +13,7 @@ option(BUILD_TESTING_OINK "" ${BUILD_TESTING}) include(FetchContent) # Find dependencies. -if(NOT TARGET roboplan::roboplan) - find_package(roboplan REQUIRED) -endif() +find_package(roboplan REQUIRED) # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -108,12 +106,6 @@ target_link_libraries(roboplan_oink OsqpEigen::OsqpEigen ) set_property(TARGET roboplan_oink PROPERTY CXX_STANDARD 20) -if(SKBUILD OR ROBOPLAN_CMEEL) - set_target_properties(roboplan_oink PROPERTIES - INSTALL_RPATH "$ORIGIN" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() install( TARGETS diff --git a/roboplan_rrt/CMakeLists.txt b/roboplan_rrt/CMakeLists.txt index 9b9ea70c..c82d2061 100644 --- a/roboplan_rrt/CMakeLists.txt +++ b/roboplan_rrt/CMakeLists.txt @@ -6,9 +6,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() # Find dependencies. -if(NOT TARGET roboplan::roboplan) - find_package(roboplan REQUIRED) -endif() +find_package(roboplan REQUIRED) # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -26,12 +24,6 @@ target_include_directories(roboplan_rrt PUBLIC ) set_property(TARGET roboplan_rrt PROPERTY CXX_STANDARD 20) target_link_libraries(roboplan_rrt roboplan::roboplan) -if(SKBUILD OR ROBOPLAN_CMEEL) - set_target_properties(roboplan_rrt PROPERTIES - INSTALL_RPATH "$ORIGIN" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() install( TARGETS diff --git a/roboplan_simple_ik/CMakeLists.txt b/roboplan_simple_ik/CMakeLists.txt index a90d7d7e..fbd51eee 100644 --- a/roboplan_simple_ik/CMakeLists.txt +++ b/roboplan_simple_ik/CMakeLists.txt @@ -6,9 +6,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() # Find dependencies. -if(NOT TARGET roboplan::roboplan) - find_package(roboplan REQUIRED) -endif() +find_package(roboplan REQUIRED) # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -28,12 +26,6 @@ target_link_libraries(roboplan_simple_ik roboplan::roboplan ) set_property(TARGET roboplan_simple_ik PROPERTY CXX_STANDARD 20) -if(SKBUILD OR ROBOPLAN_CMEEL) - set_target_properties(roboplan_simple_ik PROPERTIES - INSTALL_RPATH "$ORIGIN" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() install( TARGETS diff --git a/roboplan_toppra/CMakeLists.txt b/roboplan_toppra/CMakeLists.txt index 8a5cc116..c63fb1c6 100644 --- a/roboplan_toppra/CMakeLists.txt +++ b/roboplan_toppra/CMakeLists.txt @@ -6,9 +6,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() # Find dependencies. -if(NOT TARGET roboplan::roboplan) - find_package(roboplan REQUIRED) -endif() +find_package(roboplan REQUIRED) # Ament CMake is not immediately required, but we include it at the top so that # if it is found we can use it in build testing before requiring a @@ -52,12 +50,6 @@ target_link_libraries(roboplan_toppra roboplan::roboplan toppra::toppra ) -if(SKBUILD OR ROBOPLAN_CMEEL) - set_target_properties(roboplan_toppra PROPERTIES - INSTALL_RPATH "$ORIGIN" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() install( TARGETS From e46a136741c7667b6058cde54ee64fad83ca740a Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:55:39 -0700 Subject: [PATCH 27/52] Restore binding CMake files Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan/bindings/CMakeLists.txt | 41 ++----------------- .../bindings/CMakeLists.txt | 23 +---------- roboplan_oink/bindings/CMakeLists.txt | 23 +---------- roboplan_rrt/bindings/CMakeLists.txt | 23 +---------- roboplan_simple_ik/bindings/CMakeLists.txt | 23 +---------- roboplan_toppra/bindings/CMakeLists.txt | 23 +---------- 6 files changed, 9 insertions(+), 147 deletions(-) diff --git a/roboplan/bindings/CMakeLists.txt b/roboplan/bindings/CMakeLists.txt index ff602243..647af042 100644 --- a/roboplan/bindings/CMakeLists.txt +++ b/roboplan/bindings/CMakeLists.txt @@ -29,20 +29,12 @@ else() OPTIONAL_COMPONENTS Development.SABIModule) endif() -# Prefer nanobind from the active Python interpreter when available. This keeps -# pixi/wheel builds on the isolated build environment's nanobind while allowing -# ament builds to fall back to a system CMake package when rosdep provides -# nanobind-dev without a Python module. +# Always resolve nanobind from the active Python interpreter so we pick up the +# environment's (pixi/wheel build-env) nanobind rather than a stray system +# install (e.g. /usr/share/nanobind from apt, which lacks bundled headers). execute_process( COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) -if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) - message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") -endif() -if(ROBOPLAN_NANOBIND_PYTHON_RESULT) - unset(nanobind_ROOT) -endif() + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) # NOTE: the shared binding utility header lives @@ -68,17 +60,6 @@ set_property(TARGET _core_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_core_ext PRIVATE roboplan ) -if(SKBUILD) - set_target_properties(_core_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../lib" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -elseif(ROBOPLAN_CMEEL) - set_target_properties(_core_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../../.." - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() # PEP 420 namespace package: no roboplan/__init__.py is shipped. Resolve the # Python install destination per build context: @@ -92,9 +73,6 @@ endif() if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") -elseif(ROBOPLAN_CMEEL) - set(PYTHON_DESTINATION - "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() @@ -118,17 +96,6 @@ set_property(TARGET _filters_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_filters_ext PRIVATE filters ) -if(SKBUILD) - set_target_properties(_filters_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../lib" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -elseif(ROBOPLAN_CMEEL) - set_target_properties(_filters_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../../.." - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() install(TARGETS _core_ext LIBRARY DESTINATION "${PYTHON_DESTINATION}/roboplan/core") install(TARGETS _filters_ext LIBRARY DESTINATION "${PYTHON_DESTINATION}/roboplan/filters") diff --git a/roboplan_example_models/bindings/CMakeLists.txt b/roboplan_example_models/bindings/CMakeLists.txt index 3ae3a536..a38279cb 100644 --- a/roboplan_example_models/bindings/CMakeLists.txt +++ b/roboplan_example_models/bindings/CMakeLists.txt @@ -21,14 +21,7 @@ endif() execute_process( COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) -if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) - message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") -endif() -if(ROBOPLAN_NANOBIND_PYTHON_RESULT) - unset(nanobind_ROOT) -endif() + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) nanobind_add_module( @@ -46,17 +39,6 @@ set_property(TARGET _example_models_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_example_models_ext PRIVATE roboplan_example_models ) -if(SKBUILD) - set_target_properties(_example_models_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../lib" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -elseif(ROBOPLAN_CMEEL) - set_target_properties(_example_models_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../../.." - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -69,9 +51,6 @@ endif() if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") -elseif(ROBOPLAN_CMEEL) - set(PYTHON_DESTINATION - "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() diff --git a/roboplan_oink/bindings/CMakeLists.txt b/roboplan_oink/bindings/CMakeLists.txt index 0af0bcd4..a319ac27 100644 --- a/roboplan_oink/bindings/CMakeLists.txt +++ b/roboplan_oink/bindings/CMakeLists.txt @@ -23,14 +23,7 @@ endif() execute_process( COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) -if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) - message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") -endif() -if(ROBOPLAN_NANOBIND_PYTHON_RESULT) - unset(nanobind_ROOT) -endif() + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) nanobind_add_module( @@ -49,17 +42,6 @@ target_link_libraries(_optimal_ik_ext PRIVATE roboplan_oink roboplan::roboplan ) -if(SKBUILD) - set_target_properties(_optimal_ik_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../lib" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -elseif(ROBOPLAN_CMEEL) - set_target_properties(_optimal_ik_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../../.." - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -72,9 +54,6 @@ endif() if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") -elseif(ROBOPLAN_CMEEL) - set(PYTHON_DESTINATION - "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() diff --git a/roboplan_rrt/bindings/CMakeLists.txt b/roboplan_rrt/bindings/CMakeLists.txt index 05989580..fd560163 100644 --- a/roboplan_rrt/bindings/CMakeLists.txt +++ b/roboplan_rrt/bindings/CMakeLists.txt @@ -22,14 +22,7 @@ endif() execute_process( COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) -if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) - message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") -endif() -if(ROBOPLAN_NANOBIND_PYTHON_RESULT) - unset(nanobind_ROOT) -endif() + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) nanobind_add_module( @@ -48,17 +41,6 @@ target_link_libraries(_rrt_ext PRIVATE roboplan_rrt roboplan::roboplan ) -if(SKBUILD) - set_target_properties(_rrt_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../lib" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -elseif(ROBOPLAN_CMEEL) - set_target_properties(_rrt_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../../.." - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -71,9 +53,6 @@ endif() if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") -elseif(ROBOPLAN_CMEEL) - set(PYTHON_DESTINATION - "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() diff --git a/roboplan_simple_ik/bindings/CMakeLists.txt b/roboplan_simple_ik/bindings/CMakeLists.txt index 2d37f26c..829088ba 100644 --- a/roboplan_simple_ik/bindings/CMakeLists.txt +++ b/roboplan_simple_ik/bindings/CMakeLists.txt @@ -22,14 +22,7 @@ endif() execute_process( COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) -if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) - message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") -endif() -if(ROBOPLAN_NANOBIND_PYTHON_RESULT) - unset(nanobind_ROOT) -endif() + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) nanobind_add_module( @@ -48,17 +41,6 @@ target_link_libraries(_simple_ik_ext PRIVATE roboplan_simple_ik roboplan::roboplan ) -if(SKBUILD) - set_target_properties(_simple_ik_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../lib" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -elseif(ROBOPLAN_CMEEL) - set_target_properties(_simple_ik_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../../.." - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -71,9 +53,6 @@ endif() if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") -elseif(ROBOPLAN_CMEEL) - set(PYTHON_DESTINATION - "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() diff --git a/roboplan_toppra/bindings/CMakeLists.txt b/roboplan_toppra/bindings/CMakeLists.txt index c01e4d82..3643bd44 100644 --- a/roboplan_toppra/bindings/CMakeLists.txt +++ b/roboplan_toppra/bindings/CMakeLists.txt @@ -23,14 +23,7 @@ endif() execute_process( COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir - RESULT_VARIABLE ROBOPLAN_NANOBIND_PYTHON_RESULT - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) -if(ROBOPLAN_NANOBIND_PYTHON_RESULT AND NOT AMENT_BUILD) - message(FATAL_ERROR "Failed to resolve nanobind from ${Python_EXECUTABLE}") -endif() -if(ROBOPLAN_NANOBIND_PYTHON_RESULT) - unset(nanobind_ROOT) -endif() + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) nanobind_add_module( @@ -49,17 +42,6 @@ target_link_libraries(_toppra_ext PRIVATE roboplan_toppra roboplan::roboplan ) -if(SKBUILD) - set_target_properties(_toppra_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../lib" - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -elseif(ROBOPLAN_CMEEL) - set_target_properties(_toppra_ext PROPERTIES - INSTALL_RPATH "$ORIGIN/../../../.." - INSTALL_RPATH_USE_LINK_PATH FALSE - ) -endif() # Resolve the Python install destination per build context: # - colcon (ament): the site-packages dir (relative to the package's install @@ -72,9 +54,6 @@ endif() if(AMENT_BUILD) set(PYTHON_DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") -elseif(ROBOPLAN_CMEEL) - set(PYTHON_DESTINATION - "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") elseif(SKBUILD) set(PYTHON_DESTINATION ".") else() From 2bbc24a9c78809aeda98dca03a864bf6223f5ecc Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:55:57 -0700 Subject: [PATCH 28/52] Restore package test CMake files Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- roboplan/test/CMakeLists.txt | 8 +------- roboplan_oink/test/CMakeLists.txt | 8 +------- roboplan_rrt/test/CMakeLists.txt | 8 +------- roboplan_toppra/test/CMakeLists.txt | 8 +------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/roboplan/test/CMakeLists.txt b/roboplan/test/CMakeLists.txt index 052c6268..0a8ede06 100644 --- a/roboplan/test/CMakeLists.txt +++ b/roboplan/test/CMakeLists.txt @@ -1,11 +1,5 @@ find_package(GTest REQUIRED) -if(NOT TARGET roboplan_example_models::roboplan_example_models) - if(TARGET roboplan_example_models) - add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) - else() - find_package(roboplan_example_models REQUIRED) - endif() -endif() +find_package(roboplan_example_models REQUIRED) # Build tests add_executable(test_types test_types.cpp) diff --git a/roboplan_oink/test/CMakeLists.txt b/roboplan_oink/test/CMakeLists.txt index 488b0140..c4eba865 100644 --- a/roboplan_oink/test/CMakeLists.txt +++ b/roboplan_oink/test/CMakeLists.txt @@ -1,11 +1,5 @@ find_package(GTest REQUIRED) -if(NOT TARGET roboplan_example_models::roboplan_example_models) - if(TARGET roboplan_example_models) - add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) - else() - find_package(roboplan_example_models REQUIRED) - endif() -endif() +find_package(roboplan_example_models REQUIRED) # Build Oink integration tests add_executable(test_oink test_oink.cpp) diff --git a/roboplan_rrt/test/CMakeLists.txt b/roboplan_rrt/test/CMakeLists.txt index 6163cfb5..7742c3ed 100644 --- a/roboplan_rrt/test/CMakeLists.txt +++ b/roboplan_rrt/test/CMakeLists.txt @@ -1,11 +1,5 @@ find_package(GTest REQUIRED) -if(NOT TARGET roboplan_example_models::roboplan_example_models) - if(TARGET roboplan_example_models) - add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) - else() - find_package(roboplan_example_models REQUIRED) - endif() -endif() +find_package(roboplan_example_models REQUIRED) # Build tests add_executable(test_rrt test_rrt.cpp) diff --git a/roboplan_toppra/test/CMakeLists.txt b/roboplan_toppra/test/CMakeLists.txt index 25071901..d035748b 100644 --- a/roboplan_toppra/test/CMakeLists.txt +++ b/roboplan_toppra/test/CMakeLists.txt @@ -1,11 +1,5 @@ find_package(GTest REQUIRED) -if(NOT TARGET roboplan_example_models::roboplan_example_models) - if(TARGET roboplan_example_models) - add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models) - else() - find_package(roboplan_example_models REQUIRED) - endif() -endif() +find_package(roboplan_example_models REQUIRED) # Build tests add_executable(test_toppra test_toppra.cpp) From c72a9f12190160030d07f26a3538e832af561904 Mon Sep 17 00:00:00 2001 From: cc Date: Sun, 21 Jun 2026 19:56:14 -0700 Subject: [PATCH 29/52] Update reduced CMake contract tests Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- tests/unified_python_package_test.py | 88 +++++++++++++--------------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index cc8b2208..06b9c2fd 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -42,6 +42,7 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: tool = cast(dict[str, object], data["tool"]) scikit_build = cast(dict[str, object], tool["scikit-build"]) cmake = cast(dict[str, object], scikit_build["cmake"]) + assert cmake["source-dir"] == "packaging/python" defines = cast(dict[str, str], cmake["define"]) assert defines["CMAKE_POLICY_VERSION_MINIMUM"] == "3.5" assert defines["CMAKE_INSTALL_LIBDIR"] == "lib" @@ -53,41 +54,19 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: def test_core_cmake_finds_pinocchio_transitive_targets_explicitly() -> None: - root = _read("CMakeLists.txt") + packaging_cmake = _read("packaging/python/CMakeLists.txt") helper = _read("packaging/python/cmake/roboplan_python_packaging.cmake") - source = _read("roboplan/CMakeLists.txt") - config = _read("roboplan/cmake/roboplanConfig.cmake.in") - assert "include(packaging/python/cmake/roboplan_python_packaging.cmake)" in root - assert "roboplan_ensure_hpp_fcl_target()" in root - assert root.index("roboplan_ensure_hpp_fcl_target()") < root.index( - "add_subdirectory(roboplan)" - ) + assert "include(cmake/roboplan_python_packaging.cmake)" in packaging_cmake + assert "roboplan_ensure_hpp_fcl_target()" in packaging_cmake + assert packaging_cmake.index( + "roboplan_ensure_hpp_fcl_target()" + ) < packaging_cmake.index('add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan"') assert "find_package(hpp-fcl CONFIG QUIET)" in helper assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in helper - - assert "find_package(pinocchio REQUIRED)" in source - assert "find_package(hpp-fcl CONFIG QUIET)" in source - assert "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" in source - assert "Failed to resolve hpp-fcl::hpp-fcl" in source - assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in source - assert "hpp/fcl/fwd.hh" in source - assert "NAMES hpp-fcl" in source - assert source.index("find_package(pinocchio REQUIRED)") < source.index( - "find_package(hpp-fcl CONFIG QUIET)" - ) - assert source.index("find_package(hpp-fcl CONFIG QUIET)") < source.index( - "target_link_libraries(roboplan" - ) - - assert "find_package(pinocchio REQUIRED)" in config - assert "find_package(hpp-fcl CONFIG QUIET)" in config - assert "${ROBOPLAN_PINOCCHIO_CMAKE_DIR}/hpp-fcl" in config - assert "Failed to resolve hpp-fcl::hpp-fcl" in config - assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in config - assert "hpp/fcl/fwd.hh" in config - assert "NAMES hpp-fcl" in config + assert "hpp/fcl/fwd.hh" in helper + assert "NAMES hpp-fcl" in helper def test_cmeel_split_packages_define_native_and_python_wheels() -> None: @@ -102,7 +81,7 @@ def test_cmeel_split_packages_define_native_and_python_wheels() -> None: assert lib_build["build-backend"] == "cmeel" assert lib_project["name"] == "libroboplan" - assert lib_cmeel["source"] == "../../.." + assert lib_cmeel["source"] == "../../python" assert lib_cmeel["has-sitelib"] is False assert "-DROBOPLAN_CMEEL=ON" in lib_args assert "-DBUILD_PYTHON_BINDINGS=OFF" in lib_args @@ -116,7 +95,7 @@ def test_cmeel_split_packages_define_native_and_python_wheels() -> None: assert py_build["build-backend"] == "cmeel" assert py_project["name"] == "roboplan" - assert py_cmeel["source"] == "../../.." + assert py_cmeel["source"] == "../../python" assert "libroboplan == 0.4.0" in py_dependencies assert "pin == 4.0.0" in py_dependencies assert "-DROBOPLAN_CMEEL=ON" in py_args @@ -125,15 +104,18 @@ def test_cmeel_split_packages_define_native_and_python_wheels() -> None: def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> None: - cmake = ROOT / "CMakeLists.txt" + cmake = ROOT / "packaging/python/CMakeLists.txt" assert ( cmake.exists() - ), "root CMakeLists.txt is required for the unified roboplan wheel" + ), "packaging/python/CMakeLists.txt is required for the unified roboplan wheel" source = cmake.read_text(encoding="utf-8") helper_source = _read("packaging/python/cmake/roboplan_python_packaging.cmake") - package_order = re.findall(r"add_subdirectory\((roboplan(?:_[a-z_]+)?)\)", source) + package_order = re.findall( + r'add_subdirectory\("\$\{ROBOPLAN_REPOSITORY_ROOT\}/(roboplan(?:_[a-z_]+)?)"\s+"[^/]+"\)', + source, + ) repair_source = _read("packaging/python/cmake/repair_unified_rpaths.cmake.in") @@ -161,7 +143,22 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No ] -def test_dependent_packages_can_use_in_tree_core_target() -> None: +def test_packaging_entrypoint_provides_build_tree_package_configs() -> None: + helper = _read("packaging/python/cmake/roboplan_python_packaging.cmake") + packaging_cmake = _read("packaging/python/CMakeLists.txt") + + assert "roboplan_register_build_tree_packages()" in packaging_cmake + assert "function(roboplan_register_build_tree_package package_name)" in helper + assert "${package_name}_DIR" in helper + assert "roboplan::roboplan=roboplan" in helper + assert "roboplan::filters=filters" in helper + assert ( + "roboplan_example_models::roboplan_example_models=roboplan_example_models" + in helper + ) + + +def test_dependent_packages_keep_upstream_find_package_shape() -> None: for path in [ "roboplan_simple_ik/CMakeLists.txt", "roboplan_oink/CMakeLists.txt", @@ -170,11 +167,11 @@ def test_dependent_packages_can_use_in_tree_core_target() -> None: ]: source = _read(path) - assert "if(NOT TARGET roboplan::roboplan)" in source, path assert "find_package(roboplan REQUIRED)" in source, path + assert "if(NOT TARGET roboplan::roboplan)" not in source, path -def test_binding_packages_preserve_python_nanobind_for_non_ament_builds() -> None: +def test_binding_packages_keep_upstream_nanobind_discovery() -> None: for path in [ "roboplan/bindings/CMakeLists.txt", "roboplan_example_models/bindings/CMakeLists.txt", @@ -186,12 +183,11 @@ def test_binding_packages_preserve_python_nanobind_for_non_ament_builds() -> Non source = _read(path) assert "-m nanobind --cmake_dir" in source, path - assert "ROBOPLAN_NANOBIND_PYTHON_RESULT" in source, path - assert "AND NOT AMENT_BUILD" in source, path assert "find_package(nanobind CONFIG REQUIRED)" in source, path + assert "ROBOPLAN_NANOBIND_PYTHON_RESULT" not in source, path -def test_dependent_package_tests_can_use_in_tree_example_models_target() -> None: +def test_dependent_package_tests_keep_upstream_example_model_dependency() -> None: for path in [ "roboplan/test/CMakeLists.txt", "roboplan_oink/test/CMakeLists.txt", @@ -200,15 +196,11 @@ def test_dependent_package_tests_can_use_in_tree_example_models_target() -> None ]: source = _read(path) + assert "find_package(roboplan_example_models REQUIRED)" in source, path assert ( - "if(NOT TARGET roboplan_example_models::roboplan_example_models)" in source - ), path - assert "if(TARGET roboplan_example_models)" in source, path - assert ( - "add_library(roboplan_example_models::roboplan_example_models ALIAS roboplan_example_models)" - in source + "if(NOT TARGET roboplan_example_models::roboplan_example_models)" + not in source ), path - assert "find_package(roboplan_example_models REQUIRED)" in source, path def test_release_workflow_builds_repaired_wheels_and_uses_trusted_publishing() -> None: From d31bac9e2430d7325676a78dcbb96070f2337bb2 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 10:31:28 -0700 Subject: [PATCH 30/52] Ignore packaging helpers during colcon discovery Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/COLCON_IGNORE | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packaging/COLCON_IGNORE diff --git a/packaging/COLCON_IGNORE b/packaging/COLCON_IGNORE new file mode 100644 index 00000000..e69de29b From a26129d3004e5f607ed5747da8ed082fd20e7c66 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 10:31:55 -0700 Subject: [PATCH 31/52] Update Python packaging helper behavior Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .github/workflows/release.yml | 2 +- packaging/cmeel/libroboplan/pyproject.toml | 1 - packaging/python/CMakeLists.txt | 8 +- .../cmake/roboplan_python_packaging.cmake | 123 ++++++++---------- 4 files changed, 60 insertions(+), 74 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0001a3eb..994077cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,7 +112,7 @@ jobs: CIBW_TEST_COMMAND: >- python -c "import roboplan; import roboplan.core; import roboplan.filters; import roboplan.example_models; import roboplan.simple_ik; import roboplan.optimal_ik; import roboplan.rrt; - import roboplan.toppra; print('roboplan-dimos imports ok')" + import roboplan.toppra; import roboplan.cartesian_planning; print('roboplan-dimos imports ok')" - name: Upload wheels uses: actions/upload-artifact@v4 with: diff --git a/packaging/cmeel/libroboplan/pyproject.toml b/packaging/cmeel/libroboplan/pyproject.toml index 4ba652ad..fd8d999d 100644 --- a/packaging/cmeel/libroboplan/pyproject.toml +++ b/packaging/cmeel/libroboplan/pyproject.toml @@ -37,5 +37,4 @@ configure-args = [ "-DROBOPLAN_CMEEL=ON", "-DBUILD_PYTHON_BINDINGS=OFF", "-DBUILD_TESTING=OFF", - "-DBUILD_TESTING_OINK=OFF", ] diff --git a/packaging/python/CMakeLists.txt b/packaging/python/CMakeLists.txt index 3ab9dc49..c412568b 100644 --- a/packaging/python/CMakeLists.txt +++ b/packaging/python/CMakeLists.txt @@ -16,8 +16,6 @@ if(SKBUILD) roboplan_configure_scikit_build_prefix() endif() -roboplan_ensure_hpp_fcl_target() - if(BUILD_STANDALONE_PYTHON_BINDINGS) find_package(roboplan_example_models REQUIRED CONFIG) find_package(roboplan REQUIRED CONFIG) @@ -25,6 +23,7 @@ if(BUILD_STANDALONE_PYTHON_BINDINGS) find_package(roboplan_oink REQUIRED CONFIG) find_package(roboplan_rrt REQUIRED CONFIG) find_package(roboplan_toppra REQUIRED CONFIG) + find_package(roboplan_cartesian_planning REQUIRED CONFIG) if(TARGET roboplan_example_models::roboplan_example_models AND NOT TARGET roboplan_example_models) add_library(roboplan_example_models ALIAS roboplan_example_models::roboplan_example_models) @@ -47,6 +46,9 @@ if(BUILD_STANDALONE_PYTHON_BINDINGS) if(TARGET roboplan_toppra::roboplan_toppra AND NOT TARGET roboplan_toppra) add_library(roboplan_toppra ALIAS roboplan_toppra::roboplan_toppra) endif() + if(TARGET roboplan_cartesian_planning::roboplan_cartesian_planning AND NOT TARGET roboplan_cartesian_planning) + add_library(roboplan_cartesian_planning ALIAS roboplan_cartesian_planning::roboplan_cartesian_planning) + endif() add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_example_models/bindings" "bindings/roboplan_example_models") add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan/bindings" "bindings/roboplan") @@ -54,6 +56,7 @@ if(BUILD_STANDALONE_PYTHON_BINDINGS) add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_oink/bindings" "bindings/roboplan_oink") add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_rrt/bindings" "bindings/roboplan_rrt") add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_toppra/bindings" "bindings/roboplan_toppra") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_cartesian_planning/bindings" "bindings/roboplan_cartesian_planning") else() roboplan_register_build_tree_packages() @@ -63,6 +66,7 @@ else() add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_oink" "roboplan_oink") add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_rrt" "roboplan_rrt") add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_toppra" "roboplan_toppra") + add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_cartesian_planning" "roboplan_cartesian_planning") endif() if(SKBUILD) diff --git a/packaging/python/cmake/roboplan_python_packaging.cmake b/packaging/python/cmake/roboplan_python_packaging.cmake index b9c2d5e3..c6d73dc6 100644 --- a/packaging/python/cmake/roboplan_python_packaging.cmake +++ b/packaging/python/cmake/roboplan_python_packaging.cmake @@ -15,31 +15,6 @@ function(roboplan_configure_scikit_build_prefix) endif() endfunction() -function(roboplan_ensure_hpp_fcl_target) - find_package(hpp-fcl CONFIG QUIET) - if(TARGET hpp-fcl::hpp-fcl) - return() - endif() - - find_path(ROBOPLAN_HPP_FCL_INCLUDE_DIR - NAMES hpp/fcl/fwd.hh - HINTS "/opt/ros/$ENV{ROS_DISTRO}/include" - ) - find_library(ROBOPLAN_HPP_FCL_LIBRARY - NAMES hpp-fcl - HINTS - "/opt/ros/$ENV{ROS_DISTRO}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" - "/opt/ros/$ENV{ROS_DISTRO}/lib" - ) - if(ROBOPLAN_HPP_FCL_INCLUDE_DIR AND ROBOPLAN_HPP_FCL_LIBRARY) - add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL) - set_target_properties(hpp-fcl::hpp-fcl PROPERTIES - IMPORTED_LOCATION "${ROBOPLAN_HPP_FCL_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ROBOPLAN_HPP_FCL_INCLUDE_DIR}" - ) - endif() -endfunction() - function(roboplan_register_build_tree_package package_name) set(options) set(one_value_args) @@ -78,6 +53,33 @@ function(roboplan_register_build_tree_packages) ALIASES roboplan_rrt::roboplan_rrt=roboplan_rrt) roboplan_register_build_tree_package(roboplan_toppra ALIASES roboplan_toppra::roboplan_toppra=roboplan_toppra) + roboplan_register_build_tree_package(roboplan_cartesian_planning + ALIASES roboplan_cartesian_planning::roboplan_cartesian_planning=roboplan_cartesian_planning) +endfunction() + +function(roboplan_install_matching_libraries pattern) + foreach(search_root IN ITEMS + "$ENV{CONDA_PREFIX}/lib" + "${ROBOPLAN_CMEEL_PREFIX}/lib") + if(search_root) + file(GLOB matched_libraries LIST_DIRECTORIES false "${search_root}/${pattern}") + foreach(matched_library IN LISTS matched_libraries) + install(FILES "${matched_library}" DESTINATION lib) + file(REAL_PATH "${matched_library}" matched_real_library) + install(FILES "${matched_real_library}" DESTINATION lib) + endforeach() + endif() + endforeach() + foreach(search_prefix IN LISTS CMAKE_PREFIX_PATH) + if(search_prefix) + file(GLOB matched_libraries LIST_DIRECTORIES false "${search_prefix}/lib/${pattern}") + foreach(matched_library IN LISTS matched_libraries) + install(FILES "${matched_library}" DESTINATION lib) + file(REAL_PATH "${matched_library}" matched_real_library) + install(FILES "${matched_real_library}" DESTINATION lib) + endforeach() + endif() + endforeach() endfunction() function(roboplan_configure_unified_python_wheel) @@ -92,7 +94,8 @@ function(roboplan_configure_unified_python_wheel) roboplan_simple_ik roboplan_oink roboplan_rrt - roboplan_toppra) + roboplan_toppra + roboplan_cartesian_planning) if(TARGET ${target}) set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN") endif() @@ -105,7 +108,8 @@ function(roboplan_configure_unified_python_wheel) _simple_ik_ext _optimal_ik_ext _rrt_ext - _toppra_ext) + _toppra_ext + _cartesian_ext) if(TARGET ${target}) set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN/../../lib") endif() @@ -145,41 +149,27 @@ function(roboplan_configure_unified_python_wheel) endif() endforeach() - foreach(library IN ITEMS - libassimp.so.6 - libboost_atomic.so.1.90.0 - libboost_filesystem.so.1.88.0 - libboost_filesystem.so.1.90.0 - libboost_serialization.so.1.88.0 - libboost_serialization.so.1.90.0 - libboost_system.so.1.88.0 - libconsole_bridge.so.1.0 - libgz-math.so.9 - libgz-utils.so.4 - liboctomap.so.1.10 - liboctomath.so.1.10 - libqhull_r.so.8.0 - libsdformat.so.16 - libtinyxml2.so.11 - liburdfdom_model.so.6 - liburdfdom_sensor.so.6 - liburdfdom_world.so.6 - libyaml-cpp.so.0.8) - find_file(ROBOPLAN_UNIFIED_${library}_FILE NAMES ${library} - PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" "${ROBOPLAN_CMEEL_PREFIX}/lib" - NO_DEFAULT_PATH) - if(ROBOPLAN_UNIFIED_${library}_FILE) - install(FILES "${ROBOPLAN_UNIFIED_${library}_FILE}" DESTINATION lib) - file(REAL_PATH "${ROBOPLAN_UNIFIED_${library}_FILE}" ROBOPLAN_UNIFIED_${library}_REAL_FILE) - install(FILES "${ROBOPLAN_UNIFIED_${library}_REAL_FILE}" DESTINATION lib) - endif() + foreach(library_pattern IN ITEMS + "libassimp.so.*" + "libboost_atomic.so.*" + "libboost_filesystem.so.*" + "libboost_serialization.so.*" + "libboost_system.so.*" + "libconsole_bridge.so.*" + "libgz-math.so.*" + "libgz-utils.so.*" + "liboctomap.so.*" + "liboctomath.so.*" + "libqhull_r.so.*" + "libsdformat.so.*" + "libtinyxml2.so.*" + "liburdfdom_model.so.*" + "liburdfdom_sensor.so.*" + "liburdfdom_world.so.*" + "libyaml-cpp.so.*") + roboplan_install_matching_libraries("${library_pattern}") endforeach() - find_library(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY NAMES yaml-cpp) - if(ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY) - install(FILES "${ROBOPLAN_UNIFIED_YAML_CPP_LIBRARY}" DESTINATION lib RENAME libyaml-cpp.so.0.8) - endif() - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") configure_file( "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/repair_unified_rpaths.cmake.in" @@ -209,16 +199,9 @@ function(roboplan_configure_cmeel_package) endif() endforeach() - foreach(library IN ITEMS - libOsqpEigen.so.0.11.0) - find_file(ROBOPLAN_CMEEL_${library}_FILE NAMES ${library} - PATHS "$ENV{CONDA_PREFIX}/lib" "${CMAKE_PREFIX_PATH}/lib" - NO_DEFAULT_PATH) - if(ROBOPLAN_CMEEL_${library}_FILE) - install(FILES "${ROBOPLAN_CMEEL_${library}_FILE}" DESTINATION lib) - file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_FILE}" ROBOPLAN_CMEEL_${library}_REAL_FILE) - install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_FILE}" DESTINATION lib) - endif() + foreach(library_pattern IN ITEMS + "libOsqpEigen.so.*") + roboplan_install_matching_libraries("${library_pattern}") endforeach() configure_file( From 3d882bc943f10145f7d1dab25a50cf265123bc2e Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 10:32:12 -0700 Subject: [PATCH 32/52] Document Python packaging validation Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/python/README.md | 53 ++++++++++++++++++++++++++-- tests/unified_python_package_test.py | 42 ++++++++++++++-------- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/packaging/python/README.md b/packaging/python/README.md index 61b9aa1d..66b1159a 100644 --- a/packaging/python/README.md +++ b/packaging/python/README.md @@ -2,8 +2,8 @@ This directory contains the PyPI/cibuildwheel-specific packaging helpers for RoboPlan. The normal ROS/ament and source-tree CMake build remains in the -package directories; the root `CMakeLists.txt` includes these helpers only to -configure Python wheel behavior when `SKBUILD` or cmeel packaging is active. +package directories; `pyproject.toml` points scikit-build-core at this directory +with `tool.scikit-build.cmake.source-dir`. The helper CMake files cover three packaging-only concerns: @@ -15,3 +15,52 @@ The helper CMake files cover three packaging-only concerns: Release wheels are built by `.github/workflows/release.yml` with cibuildwheel and are smoke-tested by importing the `roboplan` namespace and every compiled submodule before publishing through PyPI trusted publishing. + +## Local checks + +Run commands from the repository root. Keep native parallelism capped on small +machines, matching CI: + +```bash +export CMAKE_BUILD_PARALLEL_LEVEL=2 +export MAKEFLAGS=-j2 +export NINJAFLAGS=-j2 +``` + +Source-build and import-test the unified wheel path: + +```bash +uv venv --seed --python 3.13 /tmp/roboplan-wheel-check +uv pip install --python /tmp/roboplan-wheel-check/bin/python --no-cache . +cd /tmp +/tmp/roboplan-wheel-check/bin/python - <<'PY' +import roboplan +import roboplan.core +import roboplan.filters +import roboplan.example_models +import roboplan.simple_ik +import roboplan.optimal_ik +import roboplan.rrt +import roboplan.toppra +import roboplan.cartesian_planning +print("roboplan imports ok") +PY +``` + +Build one Linux wheel locally with cibuildwheel and the same import smoke test +used by CI: + +```bash +CIBW_ARCHS_LINUX=x86_64 \ +CIBW_BUILD='cp313-*' \ +CIBW_SKIP='pp* *-musllinux*' \ +CIBW_MANYLINUX_X86_64_IMAGE=manylinux_2_28 \ +CIBW_ENVIRONMENT='CMAKE_BUILD_PARALLEL_LEVEL=2 MAKEFLAGS="-j2" NINJAFLAGS="-j2"' \ +uvx --from cibuildwheel cibuildwheel --platform linux +``` + +Check the structural packaging contract: + +```bash +uvx pytest tests/unified_python_package_test.py -q +``` diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 06b9c2fd..e7b8eb80 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -53,20 +53,19 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: assert ".omo/**" in sdist["exclude"] -def test_core_cmake_finds_pinocchio_transitive_targets_explicitly() -> None: +def test_packaging_directory_is_ignored_by_colcon() -> None: + assert (ROOT / "packaging/COLCON_IGNORE").exists() + + +def test_packaging_cmake_uses_scikit_build_dependency_prefix_only() -> None: packaging_cmake = _read("packaging/python/CMakeLists.txt") helper = _read("packaging/python/cmake/roboplan_python_packaging.cmake") assert "include(cmake/roboplan_python_packaging.cmake)" in packaging_cmake - assert "roboplan_ensure_hpp_fcl_target()" in packaging_cmake - assert packaging_cmake.index( - "roboplan_ensure_hpp_fcl_target()" - ) < packaging_cmake.index('add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan"') - - assert "find_package(hpp-fcl CONFIG QUIET)" in helper - assert "add_library(hpp-fcl::hpp-fcl SHARED IMPORTED GLOBAL)" in helper - assert "hpp/fcl/fwd.hh" in helper - assert "NAMES hpp-fcl" in helper + assert "roboplan_configure_scikit_build_prefix()" in packaging_cmake + assert "roboplan_ensure_hpp_fcl_target" not in packaging_cmake + assert "hpp-fcl" not in helper + assert "/opt/ros" not in helper def test_cmeel_split_packages_define_native_and_python_wheels() -> None: @@ -85,6 +84,8 @@ def test_cmeel_split_packages_define_native_and_python_wheels() -> None: assert lib_cmeel["has-sitelib"] is False assert "-DROBOPLAN_CMEEL=ON" in lib_args assert "-DBUILD_PYTHON_BINDINGS=OFF" in lib_args + assert "-DBUILD_TESTING=OFF" in lib_args + assert "-DBUILD_TESTING_OINK=OFF" not in lib_args py_build = cast(dict[str, object], roboplan["build-system"]) py_project = cast(dict[str, object], roboplan["project"]) @@ -124,11 +125,15 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No assert "cmeel.prefix" in helper_source assert "list(PREPEND CMAKE_PREFIX_PATH" in helper_source assert "${ROBOPLAN_CMEEL_PREFIX}/lib" in helper_source - assert "RENAME libyaml-cpp.so.0.8" in helper_source + assert "${search_prefix}/lib/${pattern}" in helper_source assert "boost_atomic" in helper_source - assert "libboost_atomic.so.1.90.0" in helper_source - assert "libboost_filesystem.so.1.90.0" in helper_source - assert "liburdfdom_world.so.6" in helper_source + assert "roboplan_install_matching_libraries" in helper_source + assert "libboost_atomic.so.*" in helper_source + assert "libboost_filesystem.so.*" in helper_source + assert "liburdfdom_world.so.*" in helper_source + assert "libOsqpEigen.so.*" in helper_source + assert ".so.1.90.0" not in helper_source + assert ".so.0.11.0" not in helper_source assert "install(SCRIPT" in helper_source assert "install(CODE" not in helper_source assert "--set-rpath" in repair_source @@ -140,6 +145,7 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No "roboplan_oink", "roboplan_rrt", "roboplan_toppra", + "roboplan_cartesian_planning", ] @@ -156,6 +162,10 @@ def test_packaging_entrypoint_provides_build_tree_package_configs() -> None: "roboplan_example_models::roboplan_example_models=roboplan_example_models" in helper ) + assert ( + "roboplan_cartesian_planning::roboplan_cartesian_planning=roboplan_cartesian_planning" + in helper + ) def test_dependent_packages_keep_upstream_find_package_shape() -> None: @@ -164,6 +174,7 @@ def test_dependent_packages_keep_upstream_find_package_shape() -> None: "roboplan_oink/CMakeLists.txt", "roboplan_rrt/CMakeLists.txt", "roboplan_toppra/CMakeLists.txt", + "roboplan_cartesian_planning/CMakeLists.txt", ]: source = _read(path) @@ -179,6 +190,7 @@ def test_binding_packages_keep_upstream_nanobind_discovery() -> None: "roboplan_oink/bindings/CMakeLists.txt", "roboplan_rrt/bindings/CMakeLists.txt", "roboplan_toppra/bindings/CMakeLists.txt", + "roboplan_cartesian_planning/bindings/CMakeLists.txt", ]: source = _read(path) @@ -193,6 +205,7 @@ def test_dependent_package_tests_keep_upstream_example_model_dependency() -> Non "roboplan_oink/test/CMakeLists.txt", "roboplan_rrt/test/CMakeLists.txt", "roboplan_toppra/test/CMakeLists.txt", + "roboplan_cartesian_planning/test/CMakeLists.txt", ]: source = _read(path) @@ -217,6 +230,7 @@ def test_release_workflow_builds_repaired_wheels_and_uses_trusted_publishing() - assert 'NINJAFLAGS="-j2"' in workflow assert "import roboplan; import roboplan.core" in workflow assert "roboplan.toppra" in workflow + assert "roboplan.cartesian_planning" in workflow assert "pypa/gh-action-pypi-publish@release/v1" in workflow assert "id-token: write" in workflow assert "repository-url: https://test.pypi.org/legacy/" in workflow From eaec8faf319c8efe623faac90d59739d414493de Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 10:54:32 -0700 Subject: [PATCH 33/52] Add stable Python 3.14 wheel support Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .github/workflows/release.yml | 2 +- pyproject.toml | 2 +- tests/unified_python_package_test.py | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 994077cf..c83f6c98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,7 +102,7 @@ jobs: uses: pypa/cibuildwheel@v4.1.0 env: CIBW_ARCHS_LINUX: x86_64 - CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*" + CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*" CIBW_SKIP: "pp* *-musllinux*" CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 CIBW_ENVIRONMENT: >- diff --git a/pyproject.toml b/pyproject.toml index 6298206e..24fbfd26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ name = "roboplan-dimos" version = "0.4.0" description = "Modern robot motion planning library based on Pinocchio." readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.10,<3.15" authors = [ { name = "Sebastian Castro", email = "sebas.a.castro@gmail.com" }, { name = "JafarAbdi", email = "jafar.uruc@gmail.com" }, diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index e7b8eb80..e35a1464 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -28,7 +28,7 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: assert project["name"] == "roboplan-dimos" assert project["version"] == "0.4.0" - assert project["requires-python"] == ">=3.10" + assert project["requires-python"] == ">=3.10,<3.15" assert "cmeel-eigen[build]" in build_requires assert "cmeel-yaml-cpp[build]" in build_requires assert "libpinocchio[build] == 4.0.0" in build_requires @@ -223,7 +223,9 @@ def test_release_workflow_builds_repaired_wheels_and_uses_trusted_publishing() - assert "pypa/cibuildwheel@" in workflow assert "CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28" in workflow assert "CIBW_ARCHS_LINUX: x86_64" in workflow - assert 'CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*"' in workflow + assert 'CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*"' in workflow + assert "cp315" not in workflow + assert "CIBW_ENABLE: cpython-prerelease" not in workflow assert 'CIBW_SKIP: "pp* *-musllinux*"' in workflow assert "CMAKE_BUILD_PARALLEL_LEVEL=2" in workflow assert 'MAKEFLAGS="-j2"' in workflow From 6bfbe6668c5117d343a95028a131dafe78b83c05 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 11:21:59 -0700 Subject: [PATCH 34/52] Rename unified distribution back to roboplan Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- pyproject.toml | 2 +- tests/unified_python_package_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 24fbfd26..9ae4c599 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ requires = [ build-backend = "scikit_build_core.build" [project] -name = "roboplan-dimos" +name = "roboplan" version = "0.4.0" description = "Modern robot motion planning library based on Pinocchio." readme = "README.md" diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index e35a1464..1881335c 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -26,7 +26,7 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: build_requires = cast(list[str], build_system["requires"]) project = cast(dict[str, object], data["project"]) - assert project["name"] == "roboplan-dimos" + assert project["name"] == "roboplan" assert project["version"] == "0.4.0" assert project["requires-python"] == ">=3.10,<3.15" assert "cmeel-eigen[build]" in build_requires From 6f0b17e47234b085e04667fac43342b9717b4ddc Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 11:22:35 -0700 Subject: [PATCH 35/52] Split Python binding build from release publishing Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .github/workflows/build-pythonbinding.yml | 111 +++++++++++++++++++++ .github/workflows/release.yml | 115 ++-------------------- tests/unified_python_package_test.py | 21 +++- 3 files changed, 136 insertions(+), 111 deletions(-) create mode 100644 .github/workflows/build-pythonbinding.yml diff --git a/.github/workflows/build-pythonbinding.yml b/.github/workflows/build-pythonbinding.yml new file mode 100644 index 00000000..5183b434 --- /dev/null +++ b/.github/workflows/build-pythonbinding.yml @@ -0,0 +1,111 @@ +name: Build Python bindings + +on: + pull_request: + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +env: + PACKAGE_NAME: roboplan + CMAKE_BUILD_PARALLEL_LEVEL: "2" + MAKEFLAGS: "-j2" + NINJAFLAGS: "-j2" + +jobs: + check_version: + name: Check package version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.package.outputs.version }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + - name: Read package metadata + id: package + run: | + python - <<'PY' >> "$GITHUB_OUTPUT" + import tomllib + from pathlib import Path + + pyproject = tomllib.loads(Path("pyproject.toml").read_text()) + project = pyproject["project"] + if project["name"] != "roboplan": + raise SystemExit(f"Unexpected package name: {project['name']}") + print(f"version={project['version']}") + PY + - name: Check tag matches package version + if: startsWith(github.ref, 'refs/tags/v') + env: + PACKAGE_VERSION: ${{ steps.package.outputs.version }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + TAG_VERSION="${RELEASE_TAG#v}" + if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + echo "::error::Tag $RELEASE_TAG does not match package version $PACKAGE_VERSION" + exit 1 + fi + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + needs: check_version + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Build sdist + run: | + rm -rf dist + python -m pip install --upgrade build twine + python -m build --sdist + python -m twine check dist/*.tar.gz + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: python-distributions-sdist + path: dist/roboplan-*.tar.gz + if-no-files-found: error + + build_wheels: + name: Build Linux wheels + runs-on: ubuntu-latest + needs: check_version + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + - name: Build wheels + uses: pypa/cibuildwheel@v4.1.0 + env: + CIBW_ARCHS_LINUX: x86_64 + CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*" + CIBW_SKIP: "pp* *-musllinux*" + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_ENVIRONMENT: >- + CMAKE_BUILD_PARALLEL_LEVEL=2 + MAKEFLAGS="-j2" + NINJAFLAGS="-j2" + CIBW_TEST_COMMAND: >- + python -c "import roboplan; import roboplan.core; import roboplan.filters; import roboplan.example_models; + import roboplan.simple_ik; import roboplan.optimal_ik; import roboplan.rrt; + import roboplan.toppra; import roboplan.cartesian_planning; print('roboplan imports ok')" + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: python-distributions-linux + path: wheelhouse/roboplan-*.whl + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c83f6c98..baa56ec3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,118 +18,19 @@ on: permissions: contents: read -env: - PACKAGE_NAME: roboplan-dimos - CMAKE_BUILD_PARALLEL_LEVEL: "2" - MAKEFLAGS: "-j2" - NINJAFLAGS: "-j2" - jobs: - check_version: - name: Check package version - runs-on: ubuntu-latest - outputs: - version: ${{ steps.package.outputs.version }} - steps: - - name: Check out repository - uses: actions/checkout@v4 - with: - submodules: true - persist-credentials: false - - name: Read package metadata - id: package - run: | - python - <<'PY' >> "$GITHUB_OUTPUT" - import tomllib - from pathlib import Path - - pyproject = tomllib.loads(Path("pyproject.toml").read_text()) - project = pyproject["project"] - if project["name"] != "roboplan-dimos": - raise SystemExit(f"Unexpected package name: {project['name']}") - print(f"version={project['version']}") - PY - - name: Check tag matches package version - if: startsWith(github.ref, 'refs/tags/v') - env: - PACKAGE_VERSION: ${{ steps.package.outputs.version }} - RELEASE_TAG: ${{ github.ref_name }} - run: | - TAG_VERSION="${RELEASE_TAG#v}" - if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then - echo "::error::Tag $RELEASE_TAG does not match package version $PACKAGE_VERSION" - exit 1 - fi - - build_sdist: - name: Build source distribution - runs-on: ubuntu-latest - needs: check_version - steps: - - name: Check out repository - uses: actions/checkout@v4 - with: - submodules: true - persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - name: Build sdist - run: | - rm -rf dist - python -m pip install --upgrade build twine - python -m build --sdist - python -m twine check dist/*.tar.gz - - name: Upload sdist - uses: actions/upload-artifact@v4 - with: - name: python-distributions-sdist - path: dist/roboplan_dimos-*.tar.gz - if-no-files-found: error - - build_wheels: - name: Build Linux wheels - runs-on: ubuntu-latest - needs: check_version - steps: - - name: Check out repository - uses: actions/checkout@v4 - with: - submodules: true - persist-credentials: false - - name: Build wheels - uses: pypa/cibuildwheel@v4.1.0 - env: - CIBW_ARCHS_LINUX: x86_64 - CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*" - CIBW_SKIP: "pp* *-musllinux*" - CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 - CIBW_ENVIRONMENT: >- - CMAKE_BUILD_PARALLEL_LEVEL=2 - MAKEFLAGS="-j2" - NINJAFLAGS="-j2" - CIBW_TEST_COMMAND: >- - python -c "import roboplan; import roboplan.core; import roboplan.filters; import roboplan.example_models; - import roboplan.simple_ik; import roboplan.optimal_ik; import roboplan.rrt; - import roboplan.toppra; import roboplan.cartesian_planning; print('roboplan-dimos imports ok')" - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: python-distributions-linux - path: wheelhouse/roboplan_dimos-*.whl - if-no-files-found: error + build_python_bindings: + name: Build Python bindings + uses: ./.github/workflows/build-pythonbinding.yml publish_testpypi: name: Publish to TestPyPI runs-on: ubuntu-latest - needs: - - build_sdist - - build_wheels + needs: build_python_bindings if: github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi' environment: name: testpypi - url: https://test.pypi.org/project/roboplan-dimos/ + url: https://test.pypi.org/project/roboplan/ permissions: contents: read id-token: write @@ -153,13 +54,11 @@ jobs: publish_pypi: name: Publish to PyPI runs-on: ubuntu-latest - needs: - - build_sdist - - build_wheels + needs: build_python_bindings if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') environment: name: pypi - url: https://pypi.org/project/roboplan-dimos/ + url: https://pypi.org/project/roboplan/ permissions: contents: read id-token: write diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 1881335c..06591dc2 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -216,10 +216,13 @@ def test_dependent_package_tests_keep_upstream_example_model_dependency() -> Non ), path -def test_release_workflow_builds_repaired_wheels_and_uses_trusted_publishing() -> None: - workflow = _read(".github/workflows/release.yml") +def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: + workflow = _read(".github/workflows/build-pythonbinding.yml") - assert "PACKAGE_NAME: roboplan-dimos" in workflow + assert "pull_request:" in workflow + assert "workflow_call:" in workflow + assert "PACKAGE_NAME: roboplan" in workflow + assert 'project["name"] != "roboplan"' in workflow assert "pypa/cibuildwheel@" in workflow assert "CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28" in workflow assert "CIBW_ARCHS_LINUX: x86_64" in workflow @@ -233,8 +236,20 @@ def test_release_workflow_builds_repaired_wheels_and_uses_trusted_publishing() - assert "import roboplan; import roboplan.core" in workflow assert "roboplan.toppra" in workflow assert "roboplan.cartesian_planning" in workflow + assert "dist/roboplan-*.tar.gz" in workflow + assert "wheelhouse/roboplan-*.whl" in workflow + + +def test_release_workflow_reuses_python_binding_build_and_publishes() -> None: + workflow = _read(".github/workflows/release.yml") + + assert "uses: ./.github/workflows/build-pythonbinding.yml" in workflow + assert "pypa/cibuildwheel@" not in workflow assert "pypa/gh-action-pypi-publish@release/v1" in workflow assert "id-token: write" in workflow + assert "https://test.pypi.org/project/roboplan/" in workflow + assert "https://pypi.org/project/roboplan/" in workflow assert "repository-url: https://test.pypi.org/legacy/" in workflow + assert "pattern: python-distributions-*" in workflow assert "TWINE_PASSWORD" not in workflow assert "__token__" not in workflow From ff91d527b69e010263eb595f4d4d478925e00491 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 19:23:59 -0700 Subject: [PATCH 36/52] Rename PyPI wheel build workflow Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- ...ythonbinding.yml => build-pypi-wheels.yml} | 19 ++++++++----------- .github/workflows/release.yml | 14 +++++++------- 2 files changed, 15 insertions(+), 18 deletions(-) rename .github/workflows/{build-pythonbinding.yml => build-pypi-wheels.yml} (90%) diff --git a/.github/workflows/build-pythonbinding.yml b/.github/workflows/build-pypi-wheels.yml similarity index 90% rename from .github/workflows/build-pythonbinding.yml rename to .github/workflows/build-pypi-wheels.yml index 5183b434..c2cc8a27 100644 --- a/.github/workflows/build-pythonbinding.yml +++ b/.github/workflows/build-pypi-wheels.yml @@ -1,4 +1,4 @@ -name: Build Python bindings +name: Build PyPI wheels on: pull_request: @@ -8,12 +8,6 @@ on: permissions: contents: read -env: - PACKAGE_NAME: roboplan - CMAKE_BUILD_PARALLEL_LEVEL: "2" - MAKEFLAGS: "-j2" - NINJAFLAGS: "-j2" - jobs: check_version: name: Check package version @@ -40,13 +34,16 @@ jobs: print(f"version={project['version']}") PY - name: Check tag matches package version - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/') env: PACKAGE_VERSION: ${{ steps.package.outputs.version }} RELEASE_TAG: ${{ github.ref_name }} run: | - TAG_VERSION="${RELEASE_TAG#v}" - if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + if ! [[ "$RELEASE_TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Tag $RELEASE_TAG must use N.N.N format" + exit 1 + fi + if [ "$RELEASE_TAG" != "$PACKAGE_VERSION" ]; then echo "::error::Tag $RELEASE_TAG does not match package version $PACKAGE_VERSION" exit 1 fi @@ -64,7 +61,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.13" + python-version: "3.12" - name: Build sdist run: | rm -rf dist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index baa56ec3..362bf5a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: tags: - - "v*" + - "[0-9]*.[0-9]*.[0-9]*" workflow_dispatch: inputs: publish_target: @@ -19,14 +19,14 @@ permissions: contents: read jobs: - build_python_bindings: - name: Build Python bindings - uses: ./.github/workflows/build-pythonbinding.yml + build_pypi_wheels: + name: Build PyPI wheels + uses: ./.github/workflows/build-pypi-wheels.yml publish_testpypi: name: Publish to TestPyPI runs-on: ubuntu-latest - needs: build_python_bindings + needs: build_pypi_wheels if: github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi' environment: name: testpypi @@ -54,8 +54,8 @@ jobs: publish_pypi: name: Publish to PyPI runs-on: ubuntu-latest - needs: build_python_bindings - if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') + needs: build_pypi_wheels + if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') environment: name: pypi url: https://pypi.org/project/roboplan/ From e88d08e1b4050b90d2c6d761c9158dddcb22b80d Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 19:24:23 -0700 Subject: [PATCH 37/52] Simplify cmeel Python wheel controls Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/cmeel/roboplan/pyproject.toml | 2 -- packaging/python/CMakeLists.txt | 4 ++++ tests/unified_python_package_test.py | 14 +++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packaging/cmeel/roboplan/pyproject.toml b/packaging/cmeel/roboplan/pyproject.toml index 8e0ccae1..16b47204 100644 --- a/packaging/cmeel/roboplan/pyproject.toml +++ b/packaging/cmeel/roboplan/pyproject.toml @@ -38,8 +38,6 @@ run-tests = false configure-args = [ "-DROBOPLAN_CMEEL=ON", "-DBUILD_STANDALONE_PYTHON_BINDINGS=ON", - "-DBUILD_PYTHON_BINDINGS=ON", "-DBUILD_TESTING=OFF", - "-DBUILD_TESTING_OINK=OFF", "-DGENERATE_PYTHON_STUBS=OFF", ] diff --git a/packaging/python/CMakeLists.txt b/packaging/python/CMakeLists.txt index c412568b..01e3e230 100644 --- a/packaging/python/CMakeLists.txt +++ b/packaging/python/CMakeLists.txt @@ -10,6 +10,10 @@ option( OFF ) +# The default path builds the unified PyPI wheel in one CMake tree. The +# standalone path is used by the cmeel split Python package, which builds only +# bindings against the already-installed native libroboplan package. + include(cmake/roboplan_python_packaging.cmake) if(SKBUILD) diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 06591dc2..db532928 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -101,6 +101,8 @@ def test_cmeel_split_packages_define_native_and_python_wheels() -> None: assert "pin == 4.0.0" in py_dependencies assert "-DROBOPLAN_CMEEL=ON" in py_args assert "-DBUILD_STANDALONE_PYTHON_BINDINGS=ON" in py_args + assert "-DBUILD_PYTHON_BINDINGS=ON" not in py_args + assert "-DBUILD_TESTING_OINK=OFF" not in py_args assert "-DGENERATE_PYTHON_STUBS=OFF" in py_args @@ -217,12 +219,16 @@ def test_dependent_package_tests_keep_upstream_example_model_dependency() -> Non def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: - workflow = _read(".github/workflows/build-pythonbinding.yml") + workflow = _read(".github/workflows/build-pypi-wheels.yml") assert "pull_request:" in workflow assert "workflow_call:" in workflow - assert "PACKAGE_NAME: roboplan" in workflow + assert "name: Build PyPI wheels" in workflow + assert "PACKAGE_NAME:" not in workflow assert 'project["name"] != "roboplan"' in workflow + assert 'python-version: "3.12"' in workflow + assert "RELEASE_TAG#v" not in workflow + assert "^[0-9]+\\.[0-9]+\\.[0-9]+$" in workflow assert "pypa/cibuildwheel@" in workflow assert "CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28" in workflow assert "CIBW_ARCHS_LINUX: x86_64" in workflow @@ -243,7 +249,9 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: def test_release_workflow_reuses_python_binding_build_and_publishes() -> None: workflow = _read(".github/workflows/release.yml") - assert "uses: ./.github/workflows/build-pythonbinding.yml" in workflow + assert '"v*"' not in workflow + assert '"[0-9]*.[0-9]*.[0-9]*"' in workflow + assert "uses: ./.github/workflows/build-pypi-wheels.yml" in workflow assert "pypa/cibuildwheel@" not in workflow assert "pypa/gh-action-pypi-publish@release/v1" in workflow assert "id-token: write" in workflow From da34dbb609ad10e2303e4b6c27f34146ead064da Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 19:24:53 -0700 Subject: [PATCH 38/52] Document PyPI wheel build scope Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- packaging/python/README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packaging/python/README.md b/packaging/python/README.md index 66b1159a..3cd7e86b 100644 --- a/packaging/python/README.md +++ b/packaging/python/README.md @@ -12,9 +12,16 @@ The helper CMake files cover three packaging-only concerns: - repair installed RPATHs with `patchelf` using checked-in CMake script templates rather than large embedded `install(CODE ...)` strings. -Release wheels are built by `.github/workflows/release.yml` with cibuildwheel -and are smoke-tested by importing the `roboplan` namespace and every compiled -submodule before publishing through PyPI trusted publishing. +Release wheels are built by `.github/workflows/build-pypi-wheels.yml` with +cibuildwheel and are smoke-tested by importing the `roboplan` namespace and +every compiled submodule. That workflow runs on pull requests so wheel build +breakage is caught before release. `.github/workflows/release.yml` invokes the +same build workflow from the tagged commit, downloads those fresh artifacts, and +publishes them through PyPI trusted publishing. + +The initial wheel target is Linux x86_64 (`manylinux_2_28`). macOS and arm64 can +be added later with cibuildwheel once native dependency bundling has matching +import-smoke validation on those platforms. ## Local checks From f5160e98763bafc773d596f746e3562f03052fb5 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 19:32:02 -0700 Subject: [PATCH 39/52] Add arm and macOS PyPI wheel builds Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .github/workflows/build-pypi-wheels.yml | 29 +++++++++++++++++++++---- packaging/python/README.md | 9 ++++---- tests/unified_python_package_test.py | 15 ++++++++++++- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-pypi-wheels.yml b/.github/workflows/build-pypi-wheels.yml index c2cc8a27..63b20135 100644 --- a/.github/workflows/build-pypi-wheels.yml +++ b/.github/workflows/build-pypi-wheels.yml @@ -76,9 +76,29 @@ jobs: if-no-files-found: error build_wheels: - name: Build Linux wheels - runs-on: ubuntu-latest + name: Build ${{ matrix.name }} wheels + runs-on: ${{ matrix.os }} needs: check_version + strategy: + fail-fast: false + matrix: + include: + - name: Linux x86_64 + os: ubuntu-latest + archs: x86_64 + artifact: linux-x86_64 + - name: Linux aarch64 + os: ubuntu-24.04-arm + archs: aarch64 + artifact: linux-aarch64 + - name: macOS x86_64 + os: macos-13 + archs: x86_64 + artifact: macos-x86_64 + - name: macOS arm64 + os: macos-14 + archs: arm64 + artifact: macos-arm64 steps: - name: Check out repository uses: actions/checkout@v4 @@ -88,10 +108,11 @@ jobs: - name: Build wheels uses: pypa/cibuildwheel@v4.1.0 env: - CIBW_ARCHS_LINUX: x86_64 + CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*" CIBW_SKIP: "pp* *-musllinux*" CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28 CIBW_ENVIRONMENT: >- CMAKE_BUILD_PARALLEL_LEVEL=2 MAKEFLAGS="-j2" @@ -103,6 +124,6 @@ jobs: - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: python-distributions-linux + name: python-distributions-${{ matrix.artifact }} path: wheelhouse/roboplan-*.whl if-no-files-found: error diff --git a/packaging/python/README.md b/packaging/python/README.md index 3cd7e86b..d09d4c6a 100644 --- a/packaging/python/README.md +++ b/packaging/python/README.md @@ -19,9 +19,9 @@ breakage is caught before release. `.github/workflows/release.yml` invokes the same build workflow from the tagged commit, downloads those fresh artifacts, and publishes them through PyPI trusted publishing. -The initial wheel target is Linux x86_64 (`manylinux_2_28`). macOS and arm64 can -be added later with cibuildwheel once native dependency bundling has matching -import-smoke validation on those platforms. +The wheel target matrix is Linux x86_64, Linux aarch64, macOS x86_64, and macOS +arm64. Linux wheels use `manylinux_2_28`; macOS wheels are built on native Intel +and Apple Silicon runners. Every wheel job runs the same import-smoke validation. ## Local checks @@ -55,7 +55,8 @@ PY ``` Build one Linux wheel locally with cibuildwheel and the same import smoke test -used by CI: +used by CI. GitHub Actions builds the remaining Linux aarch64 and macOS wheels +on hosted runners: ```bash CIBW_ARCHS_LINUX=x86_64 \ diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index db532928..3b0d10de 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -230,8 +230,20 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: assert "RELEASE_TAG#v" not in workflow assert "^[0-9]+\\.[0-9]+\\.[0-9]+$" in workflow assert "pypa/cibuildwheel@" in workflow + assert "fail-fast: false" in workflow + assert "ubuntu-24.04-arm" in workflow + assert "macos-13" in workflow + assert "macos-14" in workflow assert "CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28" in workflow - assert "CIBW_ARCHS_LINUX: x86_64" in workflow + assert "CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28" in workflow + assert "CIBW_ARCHS: ${{ matrix.archs }}" in workflow + assert "archs: x86_64" in workflow + assert "archs: aarch64" in workflow + assert "archs: arm64" in workflow + assert "artifact: linux-x86_64" in workflow + assert "artifact: linux-aarch64" in workflow + assert "artifact: macos-x86_64" in workflow + assert "artifact: macos-arm64" in workflow assert 'CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*"' in workflow assert "cp315" not in workflow assert "CIBW_ENABLE: cpython-prerelease" not in workflow @@ -243,6 +255,7 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: assert "roboplan.toppra" in workflow assert "roboplan.cartesian_planning" in workflow assert "dist/roboplan-*.tar.gz" in workflow + assert "python-distributions-${{ matrix.artifact }}" in workflow assert "wheelhouse/roboplan-*.whl" in workflow From 7c9c95b5aafc98c160399e7ead375c1fe131e4d0 Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 19:55:38 -0700 Subject: [PATCH 40/52] Set C++ standard for wheel builds Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- pyproject.toml | 2 ++ tests/unified_python_package_test.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9ae4c599..018fb827 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,8 @@ cmake.define.BUILD_PYTHON_BINDINGS = "ON" cmake.define.BUILD_TESTING = "OFF" cmake.define.GENERATE_PYTHON_STUBS = "OFF" cmake.define.CMAKE_POLICY_VERSION_MINIMUM = "3.5" +cmake.define.CMAKE_CXX_STANDARD = "17" +cmake.define.CMAKE_CXX_STANDARD_REQUIRED = "ON" cmake.define.CMAKE_INSTALL_LIBDIR = "lib" cmake.define.CMAKE_INSTALL_RPATH = "$ORIGIN/../../lib" cmake.define.CMAKE_INSTALL_RPATH_USE_LINK_PATH = "FALSE" diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 3b0d10de..fee4a956 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -45,6 +45,8 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: assert cmake["source-dir"] == "packaging/python" defines = cast(dict[str, str], cmake["define"]) assert defines["CMAKE_POLICY_VERSION_MINIMUM"] == "3.5" + assert defines["CMAKE_CXX_STANDARD"] == "17" + assert defines["CMAKE_CXX_STANDARD_REQUIRED"] == "ON" assert defines["CMAKE_INSTALL_LIBDIR"] == "lib" assert defines["CMAKE_INSTALL_RPATH"] == "$ORIGIN/../../lib" assert defines["CMAKE_INSTALL_RPATH_USE_LINK_PATH"] == "FALSE" From e4c96f0b5866d76d90d16dd4d549655ddaa6683b Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 21:03:25 -0700 Subject: [PATCH 41/52] Repair macOS wheel rpaths --- .../repair_unified_macos_rpaths.cmake.in | 55 +++++++++++++++++++ .../cmake/roboplan_python_packaging.cmake | 40 +++++++++++++- tests/unified_python_package_test.py | 11 ++++ 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 packaging/python/cmake/repair_unified_macos_rpaths.cmake.in diff --git a/packaging/python/cmake/repair_unified_macos_rpaths.cmake.in b/packaging/python/cmake/repair_unified_macos_rpaths.cmake.in new file mode 100644 index 00000000..8bb289d6 --- /dev/null +++ b/packaging/python/cmake/repair_unified_macos_rpaths.cmake.in @@ -0,0 +1,55 @@ +function(_roboplan_resolve_macos_patch_file file result) + set(_patch_file "${file}") + if(IS_SYMLINK "${_patch_file}") + file(READ_SYMLINK "${_patch_file}" _symlink_target) + if(IS_ABSOLUTE "${_symlink_target}") + set(${result} "" PARENT_SCOPE) + return() + endif() + get_filename_component(_symlink_dir "${_patch_file}" DIRECTORY) + set(_patch_file "${_symlink_dir}/${_symlink_target}") + endif() + + set(${result} "${_patch_file}" PARENT_SCOPE) +endfunction() + +function(_roboplan_add_macos_rpath file rpath) + _roboplan_resolve_macos_patch_file("${file}" _patch_file) + if(NOT _patch_file OR NOT EXISTS "${_patch_file}") + return() + endif() + + execute_process( + COMMAND otool -l "${_patch_file}" + OUTPUT_VARIABLE _otool_output + ERROR_QUIET + RESULT_VARIABLE _otool_result + ) + if(NOT _otool_result EQUAL 0) + return() + endif() + + string(FIND "${_otool_output}" "path ${rpath} (offset" _existing_rpath) + if(_existing_rpath GREATER_EQUAL 0) + return() + endif() + + execute_process( + COMMAND chmod u+w "${_patch_file}" + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND "@ROBOPLAN_UNIFIED_INSTALL_NAME_TOOL@" -add_rpath "${rpath}" "${_patch_file}" + COMMAND_ERROR_IS_FATAL ANY + ) +endfunction() + +file(GLOB_RECURSE _roboplan_extensions "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/roboplan/*.so") +foreach(_file IN LISTS _roboplan_extensions) + _roboplan_add_macos_rpath("${_file}" "@loader_path/../../lib") +endforeach() + +file(GLOB _roboplan_libraries "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/*.dylib") +foreach(_file IN LISTS _roboplan_libraries) + _roboplan_add_macos_rpath("${_file}" "@loader_path") +endforeach() diff --git a/packaging/python/cmake/roboplan_python_packaging.cmake b/packaging/python/cmake/roboplan_python_packaging.cmake index c6d73dc6..69329a04 100644 --- a/packaging/python/cmake/roboplan_python_packaging.cmake +++ b/packaging/python/cmake/roboplan_python_packaging.cmake @@ -85,6 +85,16 @@ endfunction() function(roboplan_configure_unified_python_wheel) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_program(ROBOPLAN_UNIFIED_PATCHELF patchelf REQUIRED) + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + find_program(ROBOPLAN_UNIFIED_INSTALL_NAME_TOOL install_name_tool REQUIRED) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(ROBOPLAN_UNIFIED_LIBRARY_INSTALL_RPATH "@loader_path") + set(ROBOPLAN_UNIFIED_EXTENSION_INSTALL_RPATH "@loader_path/../../lib") + else() + set(ROBOPLAN_UNIFIED_LIBRARY_INSTALL_RPATH "$ORIGIN") + set(ROBOPLAN_UNIFIED_EXTENSION_INSTALL_RPATH "$ORIGIN/../../lib") endif() foreach(target IN ITEMS @@ -97,7 +107,7 @@ function(roboplan_configure_unified_python_wheel) roboplan_toppra roboplan_cartesian_planning) if(TARGET ${target}) - set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN") + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "${ROBOPLAN_UNIFIED_LIBRARY_INSTALL_RPATH}") endif() endforeach() @@ -111,7 +121,7 @@ function(roboplan_configure_unified_python_wheel) _toppra_ext _cartesian_ext) if(TARGET ${target}) - set_property(TARGET ${target} PROPERTY INSTALL_RPATH "$ORIGIN/../../lib") + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "${ROBOPLAN_UNIFIED_EXTENSION_INSTALL_RPATH}") endif() endforeach() @@ -166,7 +176,24 @@ function(roboplan_configure_unified_python_wheel) "liburdfdom_model.so.*" "liburdfdom_sensor.so.*" "liburdfdom_world.so.*" - "libyaml-cpp.so.*") + "libyaml-cpp.so.*" + "libassimp.*.dylib" + "libboost_atomic.*.dylib" + "libboost_filesystem.*.dylib" + "libboost_serialization.*.dylib" + "libboost_system.*.dylib" + "libconsole_bridge.*.dylib" + "libgz-math.*.dylib" + "libgz-utils.*.dylib" + "liboctomap.*.dylib" + "liboctomath.*.dylib" + "libqhull_r.*.dylib" + "libsdformat.*.dylib" + "libtinyxml2.*.dylib" + "liburdfdom_model.*.dylib" + "liburdfdom_sensor.*.dylib" + "liburdfdom_world.*.dylib" + "libyaml-cpp.*.dylib") roboplan_install_matching_libraries("${library_pattern}") endforeach() @@ -177,6 +204,13 @@ function(roboplan_configure_unified_python_wheel) @ONLY ) install(SCRIPT "${PROJECT_BINARY_DIR}/roboplan_repair_unified_rpaths.cmake") + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + configure_file( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/repair_unified_macos_rpaths.cmake.in" + "${PROJECT_BINARY_DIR}/roboplan_repair_unified_macos_rpaths.cmake" + @ONLY + ) + install(SCRIPT "${PROJECT_BINARY_DIR}/roboplan_repair_unified_macos_rpaths.cmake") endif() endfunction() diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index fee4a956..c42cdec3 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -123,6 +123,9 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No ) repair_source = _read("packaging/python/cmake/repair_unified_rpaths.cmake.in") + macos_repair_source = _read( + "packaging/python/cmake/repair_unified_macos_rpaths.cmake.in" + ) assert "roboplan_configure_scikit_build_prefix()" in source assert "roboplan_configure_unified_python_wheel()" in source @@ -135,6 +138,8 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No assert "libboost_atomic.so.*" in helper_source assert "libboost_filesystem.so.*" in helper_source assert "liburdfdom_world.so.*" in helper_source + assert "liburdfdom_world.*.dylib" in helper_source + assert "liboctomap.*.dylib" in helper_source assert "libOsqpEigen.so.*" in helper_source assert ".so.1.90.0" not in helper_source assert ".so.0.11.0" not in helper_source @@ -142,6 +147,12 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No assert "install(CODE" not in helper_source assert "--set-rpath" in repair_source assert "$ORIGIN/../../lib" in helper_source + assert "install_name_tool" in helper_source + assert "repair_unified_macos_rpaths.cmake" in helper_source + assert "@loader_path/../../lib" in helper_source + assert "-add_rpath" in macos_repair_source + assert "@loader_path/../../lib" in macos_repair_source + assert "@loader_path" in macos_repair_source assert package_order == [ "roboplan_example_models", "roboplan", From 36a6994a6f7336019284b05a16d78a7925ffa08a Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 22:01:31 -0700 Subject: [PATCH 42/52] Run PyPI wheel builds on main pushes --- .github/workflows/build-pypi-wheels.yml | 3 +++ tests/unified_python_package_test.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build-pypi-wheels.yml b/.github/workflows/build-pypi-wheels.yml index 63b20135..26d48665 100644 --- a/.github/workflows/build-pypi-wheels.yml +++ b/.github/workflows/build-pypi-wheels.yml @@ -2,6 +2,9 @@ name: Build PyPI wheels on: pull_request: + push: + branches: + - main workflow_dispatch: workflow_call: diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index c42cdec3..671a26e8 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -235,6 +235,9 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: workflow = _read(".github/workflows/build-pypi-wheels.yml") assert "pull_request:" in workflow + assert "push:" in workflow + assert "branches:" in workflow + assert "- main" in workflow assert "workflow_call:" in workflow assert "name: Build PyPI wheels" in workflow assert "PACKAGE_NAME:" not in workflow From 1f73a06516efc233b5f83d7aaf93c62ba20eab2d Mon Sep 17 00:00:00 2001 From: cc Date: Mon, 22 Jun 2026 22:12:17 -0700 Subject: [PATCH 43/52] Set macOS wheel deployment targets --- .github/workflows/build-pypi-wheels.yml | 5 +++++ tests/unified_python_package_test.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/build-pypi-wheels.yml b/.github/workflows/build-pypi-wheels.yml index 26d48665..cf27ab70 100644 --- a/.github/workflows/build-pypi-wheels.yml +++ b/.github/workflows/build-pypi-wheels.yml @@ -90,18 +90,22 @@ jobs: os: ubuntu-latest archs: x86_64 artifact: linux-x86_64 + extra_environment: "" - name: Linux aarch64 os: ubuntu-24.04-arm archs: aarch64 artifact: linux-aarch64 + extra_environment: "" - name: macOS x86_64 os: macos-13 archs: x86_64 artifact: macos-x86_64 + extra_environment: "MACOSX_DEPLOYMENT_TARGET=13.0" - name: macOS arm64 os: macos-14 archs: arm64 artifact: macos-arm64 + extra_environment: "MACOSX_DEPLOYMENT_TARGET=14.0" steps: - name: Check out repository uses: actions/checkout@v4 @@ -120,6 +124,7 @@ jobs: CMAKE_BUILD_PARALLEL_LEVEL=2 MAKEFLAGS="-j2" NINJAFLAGS="-j2" + ${{ matrix.extra_environment }} CIBW_TEST_COMMAND: >- python -c "import roboplan; import roboplan.core; import roboplan.filters; import roboplan.example_models; import roboplan.simple_ik; import roboplan.optimal_ik; import roboplan.rrt; diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 671a26e8..ea1beeaa 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -260,6 +260,9 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: assert "artifact: linux-aarch64" in workflow assert "artifact: macos-x86_64" in workflow assert "artifact: macos-arm64" in workflow + assert 'extra_environment: "MACOSX_DEPLOYMENT_TARGET=13.0"' in workflow + assert 'extra_environment: "MACOSX_DEPLOYMENT_TARGET=14.0"' in workflow + assert "${{ matrix.extra_environment }}" in workflow assert 'CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*"' in workflow assert "cp315" not in workflow assert "CIBW_ENABLE: cpython-prerelease" not in workflow From f90938f10b8a139b6098b910a2a92abb53a0306f Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 23 Jun 2026 10:15:31 -0700 Subject: [PATCH 44/52] Simplify standalone binding aliases --- packaging/python/CMakeLists.txt | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/packaging/python/CMakeLists.txt b/packaging/python/CMakeLists.txt index 01e3e230..7a59fe58 100644 --- a/packaging/python/CMakeLists.txt +++ b/packaging/python/CMakeLists.txt @@ -29,30 +29,14 @@ if(BUILD_STANDALONE_PYTHON_BINDINGS) find_package(roboplan_toppra REQUIRED CONFIG) find_package(roboplan_cartesian_planning REQUIRED CONFIG) - if(TARGET roboplan_example_models::roboplan_example_models AND NOT TARGET roboplan_example_models) - add_library(roboplan_example_models ALIAS roboplan_example_models::roboplan_example_models) - endif() - if(TARGET roboplan::roboplan AND NOT TARGET roboplan) - add_library(roboplan ALIAS roboplan::roboplan) - endif() - if(TARGET roboplan::filters AND NOT TARGET filters) - add_library(filters ALIAS roboplan::filters) - endif() - if(TARGET roboplan_simple_ik::roboplan_simple_ik AND NOT TARGET roboplan_simple_ik) - add_library(roboplan_simple_ik ALIAS roboplan_simple_ik::roboplan_simple_ik) - endif() - if(TARGET roboplan_oink::roboplan_oink AND NOT TARGET roboplan_oink) - add_library(roboplan_oink ALIAS roboplan_oink::roboplan_oink) - endif() - if(TARGET roboplan_rrt::roboplan_rrt AND NOT TARGET roboplan_rrt) - add_library(roboplan_rrt ALIAS roboplan_rrt::roboplan_rrt) - endif() - if(TARGET roboplan_toppra::roboplan_toppra AND NOT TARGET roboplan_toppra) - add_library(roboplan_toppra ALIAS roboplan_toppra::roboplan_toppra) - endif() - if(TARGET roboplan_cartesian_planning::roboplan_cartesian_planning AND NOT TARGET roboplan_cartesian_planning) - add_library(roboplan_cartesian_planning ALIAS roboplan_cartesian_planning::roboplan_cartesian_planning) - endif() + add_library(roboplan_example_models ALIAS roboplan_example_models::roboplan_example_models) + add_library(roboplan ALIAS roboplan::roboplan) + add_library(filters ALIAS roboplan::filters) + add_library(roboplan_simple_ik ALIAS roboplan_simple_ik::roboplan_simple_ik) + add_library(roboplan_oink ALIAS roboplan_oink::roboplan_oink) + add_library(roboplan_rrt ALIAS roboplan_rrt::roboplan_rrt) + add_library(roboplan_toppra ALIAS roboplan_toppra::roboplan_toppra) + add_library(roboplan_cartesian_planning ALIAS roboplan_cartesian_planning::roboplan_cartesian_planning) add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_example_models/bindings" "bindings/roboplan_example_models") add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan/bindings" "bindings/roboplan") From 02bbb243c8bcde2c6268bec242a96a0f69782b01 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 23 Jun 2026 10:46:59 -0700 Subject: [PATCH 45/52] Remove split cmeel packaging path --- packaging/cmeel/libroboplan/pyproject.toml | 40 ------------- packaging/cmeel/roboplan/pyproject.toml | 43 -------------- packaging/python/CMakeLists.txt | 58 +++---------------- .../python/cmake/repair_cmeel_rpaths.cmake.in | 49 ---------------- .../cmake/roboplan_python_packaging.cmake | 32 ---------- tests/unified_python_package_test.py | 42 +------------- 6 files changed, 11 insertions(+), 253 deletions(-) delete mode 100644 packaging/cmeel/libroboplan/pyproject.toml delete mode 100644 packaging/cmeel/roboplan/pyproject.toml delete mode 100644 packaging/python/cmake/repair_cmeel_rpaths.cmake.in diff --git a/packaging/cmeel/libroboplan/pyproject.toml b/packaging/cmeel/libroboplan/pyproject.toml deleted file mode 100644 index fd8d999d..00000000 --- a/packaging/cmeel/libroboplan/pyproject.toml +++ /dev/null @@ -1,40 +0,0 @@ -[build-system] -build-backend = "cmeel" -requires = [ - "cmeel-yaml-cpp[build]", - "cmeel[build]", - "libpinocchio[build] == 4.0.0", - "patchelf", -] - -[project] -name = "libroboplan" -version = "0.4.0" -description = "Native C++ libraries, headers, and CMake packages for RoboPlan." -readme = "../../../README.md" -license = "MIT" -requires-python = ">=3.10" -authors = [ - { name = "Sebastian Castro", email = "sebas.a.castro@gmail.com" }, - { name = "JafarAbdi", email = "jafar.uruc@gmail.com" }, -] -classifiers = [ - "License :: OSI Approved :: MIT License", -] -dependencies = [ - "cmeel-yaml-cpp", - "libpinocchio == 4.0.0", -] - -[project.urls] -Homepage = "https://github.com/sea-bass/roboplan" - -[tool.cmeel] -source = "../../python" -has-sitelib = false -run-tests = false -configure-args = [ - "-DROBOPLAN_CMEEL=ON", - "-DBUILD_PYTHON_BINDINGS=OFF", - "-DBUILD_TESTING=OFF", -] diff --git a/packaging/cmeel/roboplan/pyproject.toml b/packaging/cmeel/roboplan/pyproject.toml deleted file mode 100644 index 16b47204..00000000 --- a/packaging/cmeel/roboplan/pyproject.toml +++ /dev/null @@ -1,43 +0,0 @@ -[build-system] -build-backend = "cmeel" -requires = [ - "cmeel[build]", - "libroboplan == 0.4.0", - "nanobind >=1.3.2", - "numpy", - "patchelf", -] - -[project] -name = "roboplan" -version = "0.4.0" -description = "Python bindings for RoboPlan." -readme = "../../../README.md" -license = "MIT" -requires-python = ">=3.10" -authors = [ - { name = "Sebastian Castro", email = "sebas.a.castro@gmail.com" }, - { name = "JafarAbdi", email = "jafar.uruc@gmail.com" }, -] -classifiers = [ - "License :: OSI Approved :: MIT License", -] -dependencies = [ - "libroboplan == 0.4.0", - "matplotlib", - "numpy", - "pin == 4.0.0", -] - -[project.urls] -Homepage = "https://github.com/sea-bass/roboplan" - -[tool.cmeel] -source = "../../python" -run-tests = false -configure-args = [ - "-DROBOPLAN_CMEEL=ON", - "-DBUILD_STANDALONE_PYTHON_BINDINGS=ON", - "-DBUILD_TESTING=OFF", - "-DGENERATE_PYTHON_STUBS=OFF", -] diff --git a/packaging/python/CMakeLists.txt b/packaging/python/CMakeLists.txt index 7a59fe58..c49170d0 100644 --- a/packaging/python/CMakeLists.txt +++ b/packaging/python/CMakeLists.txt @@ -3,64 +3,22 @@ project(roboplan VERSION 0.4.0) get_filename_component(ROBOPLAN_REPOSITORY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE) -option(ROBOPLAN_CMEEL "Build RoboPlan with cmeel packaging conventions" OFF) -option( - BUILD_STANDALONE_PYTHON_BINDINGS - "Build only Python bindings against pre-installed RoboPlan native packages" - OFF -) - -# The default path builds the unified PyPI wheel in one CMake tree. The -# standalone path is used by the cmeel split Python package, which builds only -# bindings against the already-installed native libroboplan package. - include(cmake/roboplan_python_packaging.cmake) if(SKBUILD) roboplan_configure_scikit_build_prefix() endif() -if(BUILD_STANDALONE_PYTHON_BINDINGS) - find_package(roboplan_example_models REQUIRED CONFIG) - find_package(roboplan REQUIRED CONFIG) - find_package(roboplan_simple_ik REQUIRED CONFIG) - find_package(roboplan_oink REQUIRED CONFIG) - find_package(roboplan_rrt REQUIRED CONFIG) - find_package(roboplan_toppra REQUIRED CONFIG) - find_package(roboplan_cartesian_planning REQUIRED CONFIG) +roboplan_register_build_tree_packages() - add_library(roboplan_example_models ALIAS roboplan_example_models::roboplan_example_models) - add_library(roboplan ALIAS roboplan::roboplan) - add_library(filters ALIAS roboplan::filters) - add_library(roboplan_simple_ik ALIAS roboplan_simple_ik::roboplan_simple_ik) - add_library(roboplan_oink ALIAS roboplan_oink::roboplan_oink) - add_library(roboplan_rrt ALIAS roboplan_rrt::roboplan_rrt) - add_library(roboplan_toppra ALIAS roboplan_toppra::roboplan_toppra) - add_library(roboplan_cartesian_planning ALIAS roboplan_cartesian_planning::roboplan_cartesian_planning) - - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_example_models/bindings" "bindings/roboplan_example_models") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan/bindings" "bindings/roboplan") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_simple_ik/bindings" "bindings/roboplan_simple_ik") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_oink/bindings" "bindings/roboplan_oink") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_rrt/bindings" "bindings/roboplan_rrt") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_toppra/bindings" "bindings/roboplan_toppra") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_cartesian_planning/bindings" "bindings/roboplan_cartesian_planning") -else() - roboplan_register_build_tree_packages() - - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_example_models" "roboplan_example_models") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan" "roboplan") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_simple_ik" "roboplan_simple_ik") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_oink" "roboplan_oink") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_rrt" "roboplan_rrt") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_toppra" "roboplan_toppra") - add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_cartesian_planning" "roboplan_cartesian_planning") -endif() +add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_example_models" "roboplan_example_models") +add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan" "roboplan") +add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_simple_ik" "roboplan_simple_ik") +add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_oink" "roboplan_oink") +add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_rrt" "roboplan_rrt") +add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_toppra" "roboplan_toppra") +add_subdirectory("${ROBOPLAN_REPOSITORY_ROOT}/roboplan_cartesian_planning" "roboplan_cartesian_planning") if(SKBUILD) roboplan_configure_unified_python_wheel() endif() - -if(ROBOPLAN_CMEEL) - roboplan_configure_cmeel_package() -endif() diff --git a/packaging/python/cmake/repair_cmeel_rpaths.cmake.in b/packaging/python/cmake/repair_cmeel_rpaths.cmake.in deleted file mode 100644 index 8ca69492..00000000 --- a/packaging/python/cmake/repair_cmeel_rpaths.cmake.in +++ /dev/null @@ -1,49 +0,0 @@ -function(_roboplan_cmeel_patch_rpath file rpath) - set(_patch_file "${file}") - if(IS_SYMLINK "${_patch_file}") - file(READ_SYMLINK "${_patch_file}" _symlink_target) - if(IS_ABSOLUTE "${_symlink_target}") - return() - endif() - get_filename_component(_symlink_dir "${_patch_file}" DIRECTORY) - set(_patch_file "${_symlink_dir}/${_symlink_target}") - endif() - - if(EXISTS "${_patch_file}") - execute_process( - COMMAND chmod u+w "${_patch_file}" - COMMAND_ERROR_IS_FATAL ANY - ) - execute_process( - COMMAND "@ROBOPLAN_CMEEL_PATCHELF@" --set-rpath "${rpath}" "${_patch_file}" - COMMAND_ERROR_IS_FATAL ANY - ) - endif() -endfunction() - -file(GLOB_RECURSE _roboplan_cmeel_installed_shared_objects - "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/*.so" - "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/*.so.*" -) - -set(_roboplan_cmeel_extensions) -set(_roboplan_cmeel_libraries) -foreach(_file IN LISTS _roboplan_cmeel_installed_shared_objects) - if(_file MATCHES "/site-packages/roboplan/.+\\.so$") - list(APPEND _roboplan_cmeel_extensions "${_file}") - elseif(_file MATCHES "/lib/lib(filters|roboplan.*|OsqpEigen|osqp|qdldl|toppra)\\.so(\\..*)?$") - list(APPEND _roboplan_cmeel_libraries "${_file}") - endif() -endforeach() - -if(BUILD_STANDALONE_PYTHON_BINDINGS AND NOT _roboplan_cmeel_extensions) - message(FATAL_ERROR "ROBOPLAN_CMEEL RPATH repair found no Python extensions") -endif() - -foreach(_file IN LISTS _roboplan_cmeel_extensions) - _roboplan_cmeel_patch_rpath("${_file}" "$ORIGIN/../../../..") -endforeach() - -foreach(_file IN LISTS _roboplan_cmeel_libraries) - _roboplan_cmeel_patch_rpath("${_file}" "$ORIGIN") -endforeach() diff --git a/packaging/python/cmake/roboplan_python_packaging.cmake b/packaging/python/cmake/roboplan_python_packaging.cmake index 69329a04..69db891d 100644 --- a/packaging/python/cmake/roboplan_python_packaging.cmake +++ b/packaging/python/cmake/roboplan_python_packaging.cmake @@ -213,35 +213,3 @@ function(roboplan_configure_unified_python_wheel) install(SCRIPT "${PROJECT_BINARY_DIR}/roboplan_repair_unified_macos_rpaths.cmake") endif() endfunction() - -function(roboplan_configure_cmeel_package) - if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") - return() - endif() - - find_program(ROBOPLAN_CMEEL_PATCHELF patchelf REQUIRED) - foreach(library IN ITEMS - OsqpEigen - osqp - qdldl - toppra) - find_library(ROBOPLAN_CMEEL_${library}_LIBRARY NAMES ${library}) - if(ROBOPLAN_CMEEL_${library}_LIBRARY) - install(FILES "${ROBOPLAN_CMEEL_${library}_LIBRARY}" DESTINATION lib) - file(REAL_PATH "${ROBOPLAN_CMEEL_${library}_LIBRARY}" ROBOPLAN_CMEEL_${library}_REAL_LIBRARY) - install(FILES "${ROBOPLAN_CMEEL_${library}_REAL_LIBRARY}" DESTINATION lib) - endif() - endforeach() - - foreach(library_pattern IN ITEMS - "libOsqpEigen.so.*") - roboplan_install_matching_libraries("${library_pattern}") - endforeach() - - configure_file( - "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/repair_cmeel_rpaths.cmake.in" - "${PROJECT_BINARY_DIR}/roboplan_repair_cmeel_rpaths.cmake" - @ONLY - ) - install(SCRIPT "${PROJECT_BINARY_DIR}/roboplan_repair_cmeel_rpaths.cmake") -endfunction() diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index ea1beeaa..4d4d30a8 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -70,44 +70,6 @@ def test_packaging_cmake_uses_scikit_build_dependency_prefix_only() -> None: assert "/opt/ros" not in helper -def test_cmeel_split_packages_define_native_and_python_wheels() -> None: - libroboplan = _read_pyproject("packaging/cmeel/libroboplan/pyproject.toml") - roboplan = _read_pyproject("packaging/cmeel/roboplan/pyproject.toml") - - lib_build = cast(dict[str, object], libroboplan["build-system"]) - lib_project = cast(dict[str, object], libroboplan["project"]) - lib_tool = cast(dict[str, object], libroboplan["tool"]) - lib_cmeel = cast(dict[str, object], lib_tool["cmeel"]) - lib_args = cast(list[str], lib_cmeel["configure-args"]) - - assert lib_build["build-backend"] == "cmeel" - assert lib_project["name"] == "libroboplan" - assert lib_cmeel["source"] == "../../python" - assert lib_cmeel["has-sitelib"] is False - assert "-DROBOPLAN_CMEEL=ON" in lib_args - assert "-DBUILD_PYTHON_BINDINGS=OFF" in lib_args - assert "-DBUILD_TESTING=OFF" in lib_args - assert "-DBUILD_TESTING_OINK=OFF" not in lib_args - - py_build = cast(dict[str, object], roboplan["build-system"]) - py_project = cast(dict[str, object], roboplan["project"]) - py_tool = cast(dict[str, object], roboplan["tool"]) - py_cmeel = cast(dict[str, object], py_tool["cmeel"]) - py_args = cast(list[str], py_cmeel["configure-args"]) - py_dependencies = cast(list[str], py_project["dependencies"]) - - assert py_build["build-backend"] == "cmeel" - assert py_project["name"] == "roboplan" - assert py_cmeel["source"] == "../../python" - assert "libroboplan == 0.4.0" in py_dependencies - assert "pin == 4.0.0" in py_dependencies - assert "-DROBOPLAN_CMEEL=ON" in py_args - assert "-DBUILD_STANDALONE_PYTHON_BINDINGS=ON" in py_args - assert "-DBUILD_PYTHON_BINDINGS=ON" not in py_args - assert "-DBUILD_TESTING_OINK=OFF" not in py_args - assert "-DGENERATE_PYTHON_STUBS=OFF" in py_args - - def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> None: cmake = ROOT / "packaging/python/CMakeLists.txt" @@ -129,6 +91,8 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No assert "roboplan_configure_scikit_build_prefix()" in source assert "roboplan_configure_unified_python_wheel()" in source + assert "ROBOPLAN_CMEEL" not in source + assert "BUILD_STANDALONE_PYTHON_BINDINGS" not in source assert "cmeel.prefix" in helper_source assert "list(PREPEND CMAKE_PREFIX_PATH" in helper_source assert "${ROBOPLAN_CMEEL_PREFIX}/lib" in helper_source @@ -140,7 +104,6 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No assert "liburdfdom_world.so.*" in helper_source assert "liburdfdom_world.*.dylib" in helper_source assert "liboctomap.*.dylib" in helper_source - assert "libOsqpEigen.so.*" in helper_source assert ".so.1.90.0" not in helper_source assert ".so.0.11.0" not in helper_source assert "install(SCRIPT" in helper_source @@ -169,6 +132,7 @@ def test_packaging_entrypoint_provides_build_tree_package_configs() -> None: packaging_cmake = _read("packaging/python/CMakeLists.txt") assert "roboplan_register_build_tree_packages()" in packaging_cmake + assert "roboplan_configure_cmeel_package" not in helper assert "function(roboplan_register_build_tree_package package_name)" in helper assert "${package_name}_DIR" in helper assert "roboplan::roboplan=roboplan" in helper From e4a2d3755ccd0a298690349bf943e61f353318f3 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 23 Jun 2026 10:54:28 -0700 Subject: [PATCH 46/52] Publish releases only to PyPI --- .github/workflows/build-pypi-wheels.yml | 5 ---- .github/workflows/release.yml | 40 +------------------------ tests/unified_python_package_test.py | 11 +++---- 3 files changed, 7 insertions(+), 49 deletions(-) diff --git a/.github/workflows/build-pypi-wheels.yml b/.github/workflows/build-pypi-wheels.yml index cf27ab70..b847bcaf 100644 --- a/.github/workflows/build-pypi-wheels.yml +++ b/.github/workflows/build-pypi-wheels.yml @@ -96,11 +96,6 @@ jobs: archs: aarch64 artifact: linux-aarch64 extra_environment: "" - - name: macOS x86_64 - os: macos-13 - archs: x86_64 - artifact: macos-x86_64 - extra_environment: "MACOSX_DEPLOYMENT_TARGET=13.0" - name: macOS arm64 os: macos-14 archs: arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 362bf5a9..f278b234 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,15 +5,6 @@ on: tags: - "[0-9]*.[0-9]*.[0-9]*" workflow_dispatch: - inputs: - publish_target: - description: "Publish destination after building artifacts" - type: choice - options: - - none - - testpypi - - pypi - default: testpypi permissions: contents: read @@ -23,39 +14,10 @@ jobs: name: Build PyPI wheels uses: ./.github/workflows/build-pypi-wheels.yml - publish_testpypi: - name: Publish to TestPyPI - runs-on: ubuntu-latest - needs: build_pypi_wheels - if: github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi' - environment: - name: testpypi - url: https://test.pypi.org/project/roboplan/ - permissions: - contents: read - id-token: write - steps: - - name: Download distributions - uses: actions/download-artifact@v5 - with: - pattern: python-distributions-* - merge-multiple: true - path: dist - - name: Check distributions - run: | - python -m pip install --upgrade twine - python -m twine check dist/* - - name: Publish to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: dist/ - repository-url: https://test.pypi.org/legacy/ - publish_pypi: name: Publish to PyPI runs-on: ubuntu-latest needs: build_pypi_wheels - if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') environment: name: pypi url: https://pypi.org/project/roboplan/ @@ -72,9 +34,9 @@ jobs: - name: Check distributions run: | python -m pip install --upgrade twine + ls -la dist python -m twine check dist/* - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ - skip-existing: true diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 4d4d30a8..5b1ed395 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -212,7 +212,7 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: assert "pypa/cibuildwheel@" in workflow assert "fail-fast: false" in workflow assert "ubuntu-24.04-arm" in workflow - assert "macos-13" in workflow + assert "macos-13" not in workflow assert "macos-14" in workflow assert "CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28" in workflow assert "CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28" in workflow @@ -222,9 +222,9 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: assert "archs: arm64" in workflow assert "artifact: linux-x86_64" in workflow assert "artifact: linux-aarch64" in workflow - assert "artifact: macos-x86_64" in workflow + assert "artifact: macos-x86_64" not in workflow assert "artifact: macos-arm64" in workflow - assert 'extra_environment: "MACOSX_DEPLOYMENT_TARGET=13.0"' in workflow + assert 'extra_environment: "MACOSX_DEPLOYMENT_TARGET=13.0"' not in workflow assert 'extra_environment: "MACOSX_DEPLOYMENT_TARGET=14.0"' in workflow assert "${{ matrix.extra_environment }}" in workflow assert 'CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*"' in workflow @@ -250,10 +250,11 @@ def test_release_workflow_reuses_python_binding_build_and_publishes() -> None: assert "uses: ./.github/workflows/build-pypi-wheels.yml" in workflow assert "pypa/cibuildwheel@" not in workflow assert "pypa/gh-action-pypi-publish@release/v1" in workflow + assert workflow.count("pypa/gh-action-pypi-publish@release/v1") == 1 assert "id-token: write" in workflow - assert "https://test.pypi.org/project/roboplan/" in workflow + assert "testpypi" not in workflow.lower() assert "https://pypi.org/project/roboplan/" in workflow - assert "repository-url: https://test.pypi.org/legacy/" in workflow + assert "repository-url: https://test.pypi.org/legacy/" not in workflow assert "pattern: python-distributions-*" in workflow assert "TWINE_PASSWORD" not in workflow assert "__token__" not in workflow From aac3ad041b05d8f2c17cf7c6b1fe3dc1fcc5a861 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 23 Jun 2026 11:06:07 -0700 Subject: [PATCH 47/52] Remove stale packaging option assertions --- tests/unified_python_package_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 5b1ed395..39fd61b7 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -91,8 +91,6 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No assert "roboplan_configure_scikit_build_prefix()" in source assert "roboplan_configure_unified_python_wheel()" in source - assert "ROBOPLAN_CMEEL" not in source - assert "BUILD_STANDALONE_PYTHON_BINDINGS" not in source assert "cmeel.prefix" in helper_source assert "list(PREPEND CMAKE_PREFIX_PATH" in helper_source assert "${ROBOPLAN_CMEEL_PREFIX}/lib" in helper_source From 3585621496caae73b55a6a9ae04d16fb5be9fafd Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 23 Jun 2026 11:14:13 -0700 Subject: [PATCH 48/52] Clean up unified packaging tests --- tests/unified_python_package_test.py | 172 +++++++++++++-------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 39fd61b7..7771382a 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -1,27 +1,46 @@ +from collections.abc import Iterable from pathlib import Path import re import tomllib from typing import cast ROOT = Path(__file__).resolve().parents[1] +PYTHON_BINDING_PACKAGES = ( + "roboplan_example_models", + "roboplan", + "roboplan_simple_ik", + "roboplan_oink", + "roboplan_rrt", + "roboplan_toppra", + "roboplan_cartesian_planning", +) +DEPENDENT_PACKAGES = PYTHON_BINDING_PACKAGES[2:] +PACKAGES_WITH_EXAMPLE_MODEL_TEST_DEPENDENCY = ( + PYTHON_BINDING_PACKAGES[1], + *PYTHON_BINDING_PACKAGES[3:], +) def _read(path: str) -> str: return (ROOT / path).read_text(encoding="utf-8") -def _read_pyproject(path: str) -> dict[str, object]: +def _read_toml(path: str) -> dict[str, object]: return cast(dict[str, object], tomllib.loads(_read(path))) -def test_root_pyproject_defines_unified_roboplan_distribution() -> None: - pyproject = ROOT / "pyproject.toml" +def _assert_contains_all(source: str, expected: Iterable[str]) -> None: + for value in expected: + assert value in source, f"expected {value!r}" + + +def _assert_excludes_all(source: str, unexpected: Iterable[str]) -> None: + for value in unexpected: + assert value not in source, f"unexpected {value!r}" - assert ( - pyproject.exists() - ), "root pyproject.toml is required for the unified roboplan wheel" - data = cast(dict[str, object], tomllib.loads(pyproject.read_text(encoding="utf-8"))) +def test_root_pyproject_defines_unified_roboplan_distribution() -> None: + data = _read_toml("pyproject.toml") build_system = cast(dict[str, object], data["build-system"]) build_requires = cast(list[str], build_system["requires"]) project = cast(dict[str, object], data["project"]) @@ -63,21 +82,19 @@ def test_packaging_cmake_uses_scikit_build_dependency_prefix_only() -> None: packaging_cmake = _read("packaging/python/CMakeLists.txt") helper = _read("packaging/python/cmake/roboplan_python_packaging.cmake") - assert "include(cmake/roboplan_python_packaging.cmake)" in packaging_cmake - assert "roboplan_configure_scikit_build_prefix()" in packaging_cmake - assert "roboplan_ensure_hpp_fcl_target" not in packaging_cmake - assert "hpp-fcl" not in helper - assert "/opt/ros" not in helper + _assert_contains_all( + packaging_cmake, + [ + "include(cmake/roboplan_python_packaging.cmake)", + "roboplan_configure_scikit_build_prefix()", + ], + ) + _assert_excludes_all(packaging_cmake, ["roboplan_ensure_hpp_fcl_target"]) + _assert_excludes_all(helper, ["hpp-fcl", "/opt/ros"]) def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> None: - cmake = ROOT / "packaging/python/CMakeLists.txt" - - assert ( - cmake.exists() - ), "packaging/python/CMakeLists.txt is required for the unified roboplan wheel" - - source = cmake.read_text(encoding="utf-8") + source = _read("packaging/python/CMakeLists.txt") helper_source = _read("packaging/python/cmake/roboplan_python_packaging.cmake") package_order = re.findall( r'add_subdirectory\("\$\{ROBOPLAN_REPOSITORY_ROOT\}/(roboplan(?:_[a-z_]+)?)"\s+"[^/]+"\)', @@ -89,40 +106,41 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No "packaging/python/cmake/repair_unified_macos_rpaths.cmake.in" ) - assert "roboplan_configure_scikit_build_prefix()" in source - assert "roboplan_configure_unified_python_wheel()" in source - assert "cmeel.prefix" in helper_source - assert "list(PREPEND CMAKE_PREFIX_PATH" in helper_source - assert "${ROBOPLAN_CMEEL_PREFIX}/lib" in helper_source - assert "${search_prefix}/lib/${pattern}" in helper_source - assert "boost_atomic" in helper_source - assert "roboplan_install_matching_libraries" in helper_source - assert "libboost_atomic.so.*" in helper_source - assert "libboost_filesystem.so.*" in helper_source - assert "liburdfdom_world.so.*" in helper_source - assert "liburdfdom_world.*.dylib" in helper_source - assert "liboctomap.*.dylib" in helper_source - assert ".so.1.90.0" not in helper_source - assert ".so.0.11.0" not in helper_source - assert "install(SCRIPT" in helper_source - assert "install(CODE" not in helper_source + _assert_contains_all( + source, + [ + "roboplan_configure_scikit_build_prefix()", + "roboplan_configure_unified_python_wheel()", + ], + ) + _assert_contains_all( + helper_source, + [ + "cmeel.prefix", + "list(PREPEND CMAKE_PREFIX_PATH", + "${ROBOPLAN_CMEEL_PREFIX}/lib", + "${search_prefix}/lib/${pattern}", + "boost_atomic", + "roboplan_install_matching_libraries", + "libboost_atomic.so.*", + "libboost_filesystem.so.*", + "liburdfdom_world.so.*", + "liburdfdom_world.*.dylib", + "liboctomap.*.dylib", + "install(SCRIPT", + "$ORIGIN/../../lib", + "install_name_tool", + "repair_unified_macos_rpaths.cmake", + "@loader_path/../../lib", + ], + ) + _assert_excludes_all(helper_source, [".so.1.90.0", ".so.0.11.0", "install(CODE"]) assert "--set-rpath" in repair_source - assert "$ORIGIN/../../lib" in helper_source - assert "install_name_tool" in helper_source - assert "repair_unified_macos_rpaths.cmake" in helper_source - assert "@loader_path/../../lib" in helper_source - assert "-add_rpath" in macos_repair_source - assert "@loader_path/../../lib" in macos_repair_source - assert "@loader_path" in macos_repair_source - assert package_order == [ - "roboplan_example_models", - "roboplan", - "roboplan_simple_ik", - "roboplan_oink", - "roboplan_rrt", - "roboplan_toppra", - "roboplan_cartesian_planning", - ] + _assert_contains_all( + macos_repair_source, + ["-add_rpath", "@loader_path/../../lib", "@loader_path"], + ) + assert package_order == list(PYTHON_BINDING_PACKAGES) def test_packaging_entrypoint_provides_build_tree_package_configs() -> None: @@ -131,28 +149,22 @@ def test_packaging_entrypoint_provides_build_tree_package_configs() -> None: assert "roboplan_register_build_tree_packages()" in packaging_cmake assert "roboplan_configure_cmeel_package" not in helper - assert "function(roboplan_register_build_tree_package package_name)" in helper - assert "${package_name}_DIR" in helper - assert "roboplan::roboplan=roboplan" in helper - assert "roboplan::filters=filters" in helper - assert ( - "roboplan_example_models::roboplan_example_models=roboplan_example_models" - in helper - ) - assert ( - "roboplan_cartesian_planning::roboplan_cartesian_planning=roboplan_cartesian_planning" - in helper + _assert_contains_all( + helper, + [ + "function(roboplan_register_build_tree_package package_name)", + "${package_name}_DIR", + "roboplan::roboplan=roboplan", + "roboplan::filters=filters", + "roboplan_example_models::roboplan_example_models=roboplan_example_models", + "roboplan_cartesian_planning::roboplan_cartesian_planning=roboplan_cartesian_planning", + ], ) def test_dependent_packages_keep_upstream_find_package_shape() -> None: - for path in [ - "roboplan_simple_ik/CMakeLists.txt", - "roboplan_oink/CMakeLists.txt", - "roboplan_rrt/CMakeLists.txt", - "roboplan_toppra/CMakeLists.txt", - "roboplan_cartesian_planning/CMakeLists.txt", - ]: + for package in DEPENDENT_PACKAGES: + path = f"{package}/CMakeLists.txt" source = _read(path) assert "find_package(roboplan REQUIRED)" in source, path @@ -160,15 +172,8 @@ def test_dependent_packages_keep_upstream_find_package_shape() -> None: def test_binding_packages_keep_upstream_nanobind_discovery() -> None: - for path in [ - "roboplan/bindings/CMakeLists.txt", - "roboplan_example_models/bindings/CMakeLists.txt", - "roboplan_simple_ik/bindings/CMakeLists.txt", - "roboplan_oink/bindings/CMakeLists.txt", - "roboplan_rrt/bindings/CMakeLists.txt", - "roboplan_toppra/bindings/CMakeLists.txt", - "roboplan_cartesian_planning/bindings/CMakeLists.txt", - ]: + for package in PYTHON_BINDING_PACKAGES: + path = f"{package}/bindings/CMakeLists.txt" source = _read(path) assert "-m nanobind --cmake_dir" in source, path @@ -177,13 +182,8 @@ def test_binding_packages_keep_upstream_nanobind_discovery() -> None: def test_dependent_package_tests_keep_upstream_example_model_dependency() -> None: - for path in [ - "roboplan/test/CMakeLists.txt", - "roboplan_oink/test/CMakeLists.txt", - "roboplan_rrt/test/CMakeLists.txt", - "roboplan_toppra/test/CMakeLists.txt", - "roboplan_cartesian_planning/test/CMakeLists.txt", - ]: + for package in PACKAGES_WITH_EXAMPLE_MODEL_TEST_DEPENDENCY: + path = f"{package}/test/CMakeLists.txt" source = _read(path) assert "find_package(roboplan_example_models REQUIRED)" in source, path From 65feae4a50ed6ea7761d6e6c23a3855a11f5235b Mon Sep 17 00:00:00 2001 From: cc <55869557+TomCC7@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:22:00 -0700 Subject: [PATCH 49/52] Apply suggestions from code review Co-authored-by: cc <55869557+TomCC7@users.noreply.github.com> --- packaging/python/README.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packaging/python/README.md b/packaging/python/README.md index d09d4c6a..a0119523 100644 --- a/packaging/python/README.md +++ b/packaging/python/README.md @@ -19,9 +19,6 @@ breakage is caught before release. `.github/workflows/release.yml` invokes the same build workflow from the tagged commit, downloads those fresh artifacts, and publishes them through PyPI trusted publishing. -The wheel target matrix is Linux x86_64, Linux aarch64, macOS x86_64, and macOS -arm64. Linux wheels use `manylinux_2_28`; macOS wheels are built on native Intel -and Apple Silicon runners. Every wheel job runs the same import-smoke validation. ## Local checks @@ -37,7 +34,7 @@ export NINJAFLAGS=-j2 Source-build and import-test the unified wheel path: ```bash -uv venv --seed --python 3.13 /tmp/roboplan-wheel-check +uv venv --seed /tmp/roboplan-wheel-check uv pip install --python /tmp/roboplan-wheel-check/bin/python --no-cache . cd /tmp /tmp/roboplan-wheel-check/bin/python - <<'PY' @@ -54,19 +51,6 @@ print("roboplan imports ok") PY ``` -Build one Linux wheel locally with cibuildwheel and the same import smoke test -used by CI. GitHub Actions builds the remaining Linux aarch64 and macOS wheels -on hosted runners: - -```bash -CIBW_ARCHS_LINUX=x86_64 \ -CIBW_BUILD='cp313-*' \ -CIBW_SKIP='pp* *-musllinux*' \ -CIBW_MANYLINUX_X86_64_IMAGE=manylinux_2_28 \ -CIBW_ENVIRONMENT='CMAKE_BUILD_PARALLEL_LEVEL=2 MAKEFLAGS="-j2" NINJAFLAGS="-j2"' \ -uvx --from cibuildwheel cibuildwheel --platform linux -``` - Check the structural packaging contract: ```bash From 690f3a4e11111285fde1a3fc860566891a39b924 Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 23 Jun 2026 17:48:19 -0700 Subject: [PATCH 50/52] Derive wheel version from git tags --- .github/workflows/build-pypi-wheels.yml | 12 ++++++++++- .github/workflows/release.yml | 1 + packaging/python/CMakeLists.txt | 12 ++++++++++- pyproject.toml | 6 +++++- roboplan/bindings/CMakeLists.txt | 8 +++++-- .../bindings/CMakeLists.txt | 6 +++++- .../bindings/CMakeLists.txt | 6 +++++- roboplan_oink/bindings/CMakeLists.txt | 6 +++++- roboplan_rrt/bindings/CMakeLists.txt | 6 +++++- roboplan_simple_ik/bindings/CMakeLists.txt | 6 +++++- roboplan_toppra/bindings/CMakeLists.txt | 6 +++++- tests/unified_python_package_test.py | 21 ++++++++++++++++++- 12 files changed, 84 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-pypi-wheels.yml b/.github/workflows/build-pypi-wheels.yml index b847bcaf..5620b787 100644 --- a/.github/workflows/build-pypi-wheels.yml +++ b/.github/workflows/build-pypi-wheels.yml @@ -22,19 +22,27 @@ jobs: uses: actions/checkout@v4 with: submodules: true + fetch-depth: 0 persist-credentials: false - name: Read package metadata id: package run: | + python -m pip install --upgrade "setuptools-scm>=8" python - <<'PY' >> "$GITHUB_OUTPUT" import tomllib from pathlib import Path + from setuptools_scm import get_version + pyproject = tomllib.loads(Path("pyproject.toml").read_text()) project = pyproject["project"] if project["name"] != "roboplan": raise SystemExit(f"Unexpected package name: {project['name']}") - print(f"version={project['version']}") + if "version" in project: + raise SystemExit("pyproject.toml must use git-derived dynamic versioning") + if project.get("dynamic") != ["version"]: + raise SystemExit("pyproject.toml must declare dynamic = ['version']") + print(f"version={get_version(root='.', local_scheme='no-local-version')}") PY - name: Check tag matches package version if: startsWith(github.ref, 'refs/tags/') @@ -60,6 +68,7 @@ jobs: uses: actions/checkout@v4 with: submodules: true + fetch-depth: 0 persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 @@ -106,6 +115,7 @@ jobs: uses: actions/checkout@v4 with: submodules: true + fetch-depth: 0 persist-credentials: false - name: Build wheels uses: pypa/cibuildwheel@v4.1.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f278b234..6e26cb6d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,7 @@ jobs: name: Publish to PyPI runs-on: ubuntu-latest needs: build_pypi_wheels + if: startsWith(github.ref, 'refs/tags/') environment: name: pypi url: https://pypi.org/project/roboplan/ diff --git a/packaging/python/CMakeLists.txt b/packaging/python/CMakeLists.txt index c49170d0..339f2f3b 100644 --- a/packaging/python/CMakeLists.txt +++ b/packaging/python/CMakeLists.txt @@ -1,5 +1,15 @@ cmake_minimum_required(VERSION 3.22) -project(roboplan VERSION 0.4.0) + +set(ROBOPLAN_PACKAGING_VERSION "0.0.0") +if(DEFINED SKBUILD_PROJECT_VERSION) + set(ROBOPLAN_PACKAGING_VERSION "${SKBUILD_PROJECT_VERSION}") +endif() +project(roboplan VERSION "${ROBOPLAN_PACKAGING_VERSION}") + +set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +if(DEFINED SKBUILD_PROJECT_VERSION_FULL) + set(ROBOPLAN_PYTHON_VERSION "${SKBUILD_PROJECT_VERSION_FULL}") +endif() get_filename_component(ROBOPLAN_REPOSITORY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE) diff --git a/pyproject.toml b/pyproject.toml index 018fb827..39952675 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [build-system] requires = [ "scikit-build-core >=0.10", + "setuptools-scm >=8", "cmeel-eigen[build]", "cmeel-yaml-cpp[build]", "libpinocchio[build] == 4.0.0", @@ -11,7 +12,7 @@ build-backend = "scikit_build_core.build" [project] name = "roboplan" -version = "0.4.0" +dynamic = ["version"] description = "Modern robot motion planning library based on Pinocchio." readme = "README.md" requires-python = ">=3.10,<3.15" @@ -33,6 +34,7 @@ Homepage = "https://github.com/sea-bass/roboplan" [tool.scikit-build] minimum-version = "build-system.requires" +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" build-dir = "build/{wheel_tag}" cmake.source-dir = "packaging/python" cmake.define.BUILD_PYTHON_BINDINGS = "ON" @@ -47,3 +49,5 @@ cmake.define.CMAKE_INSTALL_RPATH_USE_LINK_PATH = "FALSE" [tool.scikit-build.sdist] exclude = [".omo/**"] + +[tool.setuptools_scm] diff --git a/roboplan/bindings/CMakeLists.txt b/roboplan/bindings/CMakeLists.txt index 647af042..7763ea17 100644 --- a/roboplan/bindings/CMakeLists.txt +++ b/roboplan/bindings/CMakeLists.txt @@ -37,6 +37,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) +if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) + set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +endif() + # NOTE: the shared binding utility header lives # in this package's main include tree (roboplan/include/roboplan_bindings/) and # is installed unconditionally by the top-level CMakeLists, so dependent packages @@ -55,7 +59,7 @@ nanobind_add_module( target_include_directories(_core_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_core_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_core_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _core_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_core_ext PRIVATE roboplan @@ -91,7 +95,7 @@ nanobind_add_module( target_include_directories(_filters_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_filters_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_filters_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _filters_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_filters_ext PRIVATE filters diff --git a/roboplan_cartesian_planning/bindings/CMakeLists.txt b/roboplan_cartesian_planning/bindings/CMakeLists.txt index ebf7dc13..a45a45d5 100644 --- a/roboplan_cartesian_planning/bindings/CMakeLists.txt +++ b/roboplan_cartesian_planning/bindings/CMakeLists.txt @@ -27,6 +27,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) +if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) + set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +endif() + nanobind_add_module( _cartesian_ext STABLE_ABI @@ -37,7 +41,7 @@ nanobind_add_module( target_include_directories(_cartesian_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_cartesian_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_cartesian_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _cartesian_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_cartesian_ext PRIVATE roboplan_cartesian_planning diff --git a/roboplan_example_models/bindings/CMakeLists.txt b/roboplan_example_models/bindings/CMakeLists.txt index a38279cb..b14b1727 100644 --- a/roboplan_example_models/bindings/CMakeLists.txt +++ b/roboplan_example_models/bindings/CMakeLists.txt @@ -24,6 +24,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) +if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) + set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +endif() + nanobind_add_module( _example_models_ext STABLE_ABI @@ -34,7 +38,7 @@ nanobind_add_module( target_include_directories(_example_models_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_example_models_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_example_models_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _example_models_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_example_models_ext PRIVATE roboplan_example_models diff --git a/roboplan_oink/bindings/CMakeLists.txt b/roboplan_oink/bindings/CMakeLists.txt index a319ac27..1d62c665 100644 --- a/roboplan_oink/bindings/CMakeLists.txt +++ b/roboplan_oink/bindings/CMakeLists.txt @@ -26,6 +26,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) +if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) + set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +endif() + nanobind_add_module( _optimal_ik_ext STABLE_ABI @@ -36,7 +40,7 @@ nanobind_add_module( target_include_directories(_optimal_ik_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_optimal_ik_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_optimal_ik_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _optimal_ik_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_optimal_ik_ext PRIVATE roboplan_oink diff --git a/roboplan_rrt/bindings/CMakeLists.txt b/roboplan_rrt/bindings/CMakeLists.txt index fd560163..b4203e58 100644 --- a/roboplan_rrt/bindings/CMakeLists.txt +++ b/roboplan_rrt/bindings/CMakeLists.txt @@ -25,6 +25,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) +if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) + set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +endif() + nanobind_add_module( _rrt_ext STABLE_ABI @@ -35,7 +39,7 @@ nanobind_add_module( target_include_directories(_rrt_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_rrt_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_rrt_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _rrt_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_rrt_ext PRIVATE roboplan_rrt diff --git a/roboplan_simple_ik/bindings/CMakeLists.txt b/roboplan_simple_ik/bindings/CMakeLists.txt index 829088ba..cf4a6a32 100644 --- a/roboplan_simple_ik/bindings/CMakeLists.txt +++ b/roboplan_simple_ik/bindings/CMakeLists.txt @@ -25,6 +25,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) +if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) + set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +endif() + nanobind_add_module( _simple_ik_ext STABLE_ABI @@ -35,7 +39,7 @@ nanobind_add_module( target_include_directories(_simple_ik_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_simple_ik_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_simple_ik_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _simple_ik_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_simple_ik_ext PRIVATE roboplan_simple_ik diff --git a/roboplan_toppra/bindings/CMakeLists.txt b/roboplan_toppra/bindings/CMakeLists.txt index 3643bd44..5ed9e848 100644 --- a/roboplan_toppra/bindings/CMakeLists.txt +++ b/roboplan_toppra/bindings/CMakeLists.txt @@ -26,6 +26,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) +if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) + set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") +endif() + nanobind_add_module( _toppra_ext STABLE_ABI @@ -36,7 +40,7 @@ nanobind_add_module( target_include_directories(_toppra_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_toppra_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") +target_compile_definitions(_toppra_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") set_property(TARGET _toppra_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_toppra_ext PRIVATE roboplan_toppra diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 7771382a..543ddc4c 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -46,8 +46,10 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: project = cast(dict[str, object], data["project"]) assert project["name"] == "roboplan" - assert project["version"] == "0.4.0" + assert "version" not in project + assert project["dynamic"] == ["version"] assert project["requires-python"] == ">=3.10,<3.15" + assert "setuptools-scm >=8" in build_requires assert "cmeel-eigen[build]" in build_requires assert "cmeel-yaml-cpp[build]" in build_requires assert "libpinocchio[build] == 4.0.0" in build_requires @@ -60,6 +62,11 @@ def test_root_pyproject_defines_unified_roboplan_distribution() -> None: tool = cast(dict[str, object], data["tool"]) scikit_build = cast(dict[str, object], tool["scikit-build"]) + metadata = cast(dict[str, object], scikit_build["metadata"]) + version_metadata = cast(dict[str, str], metadata["version"]) + assert version_metadata["provider"] == "scikit_build_core.metadata.setuptools_scm" + assert "setuptools_scm" in tool + cmake = cast(dict[str, object], scikit_build["cmake"]) assert cmake["source-dir"] == "packaging/python" defines = cast(dict[str, str], cmake["define"]) @@ -109,6 +116,9 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No _assert_contains_all( source, [ + "SKBUILD_PROJECT_VERSION", + "SKBUILD_PROJECT_VERSION_FULL", + "ROBOPLAN_PYTHON_VERSION", "roboplan_configure_scikit_build_prefix()", "roboplan_configure_unified_python_wheel()", ], @@ -178,6 +188,8 @@ def test_binding_packages_keep_upstream_nanobind_discovery() -> None: assert "-m nanobind --cmake_dir" in source, path assert "find_package(nanobind CONFIG REQUIRED)" in source, path + assert "ROBOPLAN_PYTHON_VERSION" in source, path + assert 'ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}"' in source, path assert "ROBOPLAN_NANOBIND_PYTHON_RESULT" not in source, path @@ -204,6 +216,12 @@ def test_python_binding_workflow_builds_repaired_wheels_for_pr_ci() -> None: assert "name: Build PyPI wheels" in workflow assert "PACKAGE_NAME:" not in workflow assert 'project["name"] != "roboplan"' in workflow + assert "fetch-depth: 0" in workflow + assert workflow.count("fetch-depth: 0") == 3 + assert "setuptools-scm>=8" in workflow + assert "get_version(root='.', local_scheme='no-local-version')" in workflow + assert 'project["version"]' not in workflow + assert "dynamic = ['version']" in workflow assert 'python-version: "3.12"' in workflow assert "RELEASE_TAG#v" not in workflow assert "^[0-9]+\\.[0-9]+\\.[0-9]+$" in workflow @@ -249,6 +267,7 @@ def test_release_workflow_reuses_python_binding_build_and_publishes() -> None: assert "pypa/cibuildwheel@" not in workflow assert "pypa/gh-action-pypi-publish@release/v1" in workflow assert workflow.count("pypa/gh-action-pypi-publish@release/v1") == 1 + assert "if: startsWith(github.ref, 'refs/tags/')" in workflow assert "id-token: write" in workflow assert "testpypi" not in workflow.lower() assert "https://pypi.org/project/roboplan/" in workflow From 287fbd6ed9ebe58e779c157044de0dd047e7f3cc Mon Sep 17 00:00:00 2001 From: cc Date: Wed, 24 Jun 2026 13:00:06 -0700 Subject: [PATCH 51/52] Use project version for Python bindings --- packaging/python/CMakeLists.txt | 5 ----- roboplan/bindings/CMakeLists.txt | 8 ++------ roboplan_cartesian_planning/bindings/CMakeLists.txt | 6 +----- roboplan_example_models/bindings/CMakeLists.txt | 6 +----- roboplan_oink/bindings/CMakeLists.txt | 6 +----- roboplan_rrt/bindings/CMakeLists.txt | 6 +----- roboplan_simple_ik/bindings/CMakeLists.txt | 6 +----- roboplan_toppra/bindings/CMakeLists.txt | 6 +----- 8 files changed, 8 insertions(+), 41 deletions(-) diff --git a/packaging/python/CMakeLists.txt b/packaging/python/CMakeLists.txt index 339f2f3b..74164f41 100644 --- a/packaging/python/CMakeLists.txt +++ b/packaging/python/CMakeLists.txt @@ -6,11 +6,6 @@ if(DEFINED SKBUILD_PROJECT_VERSION) endif() project(roboplan VERSION "${ROBOPLAN_PACKAGING_VERSION}") -set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -if(DEFINED SKBUILD_PROJECT_VERSION_FULL) - set(ROBOPLAN_PYTHON_VERSION "${SKBUILD_PROJECT_VERSION_FULL}") -endif() - get_filename_component(ROBOPLAN_REPOSITORY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE) include(cmake/roboplan_python_packaging.cmake) diff --git a/roboplan/bindings/CMakeLists.txt b/roboplan/bindings/CMakeLists.txt index 7763ea17..647af042 100644 --- a/roboplan/bindings/CMakeLists.txt +++ b/roboplan/bindings/CMakeLists.txt @@ -37,10 +37,6 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) -if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) - set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -endif() - # NOTE: the shared binding utility header lives # in this package's main include tree (roboplan/include/roboplan_bindings/) and # is installed unconditionally by the top-level CMakeLists, so dependent packages @@ -59,7 +55,7 @@ nanobind_add_module( target_include_directories(_core_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_core_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_core_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _core_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_core_ext PRIVATE roboplan @@ -95,7 +91,7 @@ nanobind_add_module( target_include_directories(_filters_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_filters_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_filters_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _filters_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_filters_ext PRIVATE filters diff --git a/roboplan_cartesian_planning/bindings/CMakeLists.txt b/roboplan_cartesian_planning/bindings/CMakeLists.txt index a45a45d5..ebf7dc13 100644 --- a/roboplan_cartesian_planning/bindings/CMakeLists.txt +++ b/roboplan_cartesian_planning/bindings/CMakeLists.txt @@ -27,10 +27,6 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) -if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) - set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -endif() - nanobind_add_module( _cartesian_ext STABLE_ABI @@ -41,7 +37,7 @@ nanobind_add_module( target_include_directories(_cartesian_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_cartesian_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_cartesian_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _cartesian_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_cartesian_ext PRIVATE roboplan_cartesian_planning diff --git a/roboplan_example_models/bindings/CMakeLists.txt b/roboplan_example_models/bindings/CMakeLists.txt index b14b1727..a38279cb 100644 --- a/roboplan_example_models/bindings/CMakeLists.txt +++ b/roboplan_example_models/bindings/CMakeLists.txt @@ -24,10 +24,6 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) -if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) - set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -endif() - nanobind_add_module( _example_models_ext STABLE_ABI @@ -38,7 +34,7 @@ nanobind_add_module( target_include_directories(_example_models_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_example_models_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_example_models_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _example_models_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_example_models_ext PRIVATE roboplan_example_models diff --git a/roboplan_oink/bindings/CMakeLists.txt b/roboplan_oink/bindings/CMakeLists.txt index 1d62c665..a319ac27 100644 --- a/roboplan_oink/bindings/CMakeLists.txt +++ b/roboplan_oink/bindings/CMakeLists.txt @@ -26,10 +26,6 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) -if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) - set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -endif() - nanobind_add_module( _optimal_ik_ext STABLE_ABI @@ -40,7 +36,7 @@ nanobind_add_module( target_include_directories(_optimal_ik_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_optimal_ik_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_optimal_ik_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _optimal_ik_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_optimal_ik_ext PRIVATE roboplan_oink diff --git a/roboplan_rrt/bindings/CMakeLists.txt b/roboplan_rrt/bindings/CMakeLists.txt index b4203e58..fd560163 100644 --- a/roboplan_rrt/bindings/CMakeLists.txt +++ b/roboplan_rrt/bindings/CMakeLists.txt @@ -25,10 +25,6 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) -if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) - set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -endif() - nanobind_add_module( _rrt_ext STABLE_ABI @@ -39,7 +35,7 @@ nanobind_add_module( target_include_directories(_rrt_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_rrt_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_rrt_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _rrt_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_rrt_ext PRIVATE roboplan_rrt diff --git a/roboplan_simple_ik/bindings/CMakeLists.txt b/roboplan_simple_ik/bindings/CMakeLists.txt index cf4a6a32..829088ba 100644 --- a/roboplan_simple_ik/bindings/CMakeLists.txt +++ b/roboplan_simple_ik/bindings/CMakeLists.txt @@ -25,10 +25,6 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) -if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) - set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -endif() - nanobind_add_module( _simple_ik_ext STABLE_ABI @@ -39,7 +35,7 @@ nanobind_add_module( target_include_directories(_simple_ik_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_simple_ik_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_simple_ik_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _simple_ik_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_simple_ik_ext PRIVATE roboplan_simple_ik diff --git a/roboplan_toppra/bindings/CMakeLists.txt b/roboplan_toppra/bindings/CMakeLists.txt index 5ed9e848..3643bd44 100644 --- a/roboplan_toppra/bindings/CMakeLists.txt +++ b/roboplan_toppra/bindings/CMakeLists.txt @@ -26,10 +26,6 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) -if(NOT DEFINED ROBOPLAN_PYTHON_VERSION) - set(ROBOPLAN_PYTHON_VERSION "${PROJECT_VERSION}") -endif() - nanobind_add_module( _toppra_ext STABLE_ABI @@ -40,7 +36,7 @@ nanobind_add_module( target_include_directories(_toppra_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) -target_compile_definitions(_toppra_ext PRIVATE ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}") +target_compile_definitions(_toppra_ext PRIVATE ROBOPLAN_VERSION="${PROJECT_VERSION}") set_property(TARGET _toppra_ext PROPERTY CXX_STANDARD 20) target_link_libraries(_toppra_ext PRIVATE roboplan_toppra From 2774363a87d5ca362e0f2ea129878082509a0b84 Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Wed, 24 Jun 2026 20:05:32 -0400 Subject: [PATCH 52/52] Fixes from testing --- README.md | 1 + packaging/python/README.md | 46 +++++++++++++++++----------- pyproject.toml | 3 +- tests/unified_python_package_test.py | 5 +-- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4486c86c..136a7c79 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ The main folders found in this repo are as follows. - `roboplan_cartesian_planning` : A Cartesian planner for following task-space paths. - `roboplan_example_models` : Contains robot models used for testing and examples. - `roboplan_examples` : Basic examples with real robot models. +- `packaging` : Files to help package wheels for release on PyPi. --- diff --git a/packaging/python/README.md b/packaging/python/README.md index a0119523..4e6c0763 100644 --- a/packaging/python/README.md +++ b/packaging/python/README.md @@ -1,29 +1,23 @@ # Python packaging -This directory contains the PyPI/cibuildwheel-specific packaging helpers for -RoboPlan. The normal ROS/ament and source-tree CMake build remains in the -package directories; `pyproject.toml` points scikit-build-core at this directory -with `tool.scikit-build.cmake.source-dir`. +This directory contains the PyPI/cibuildwheel-specific packaging helpers for RoboPlan. +The normal source-tree CMake build remains in the package directories. +`pyproject.toml` points `scikit-build-core` at this directory with `tool.scikit-build.cmake.source-dir`. The helper CMake files cover three packaging-only concerns: -- expose cmeel's isolated native dependency prefix to scikit-build-core builds; -- install the runtime native libraries that Linux wheels need at import time; -- repair installed RPATHs with `patchelf` using checked-in CMake script - templates rather than large embedded `install(CODE ...)` strings. +- expose `cmeel`'s isolated native dependency prefix to `scikit-build-core` builds; +- install the runtime native libraries that wheels need at import time; +- repair installed RPATHs with `patchelf` using checked-in CMake script templates rather than large embedded `install(CODE ...)` strings. -Release wheels are built by `.github/workflows/build-pypi-wheels.yml` with -cibuildwheel and are smoke-tested by importing the `roboplan` namespace and -every compiled submodule. That workflow runs on pull requests so wheel build -breakage is caught before release. `.github/workflows/release.yml` invokes the -same build workflow from the tagged commit, downloads those fresh artifacts, and -publishes them through PyPI trusted publishing. +Release wheels are built with `cibuildwheel` in the [`build-pypi-wheels.yml`](../../.github/workflows/build-pypi-wheels.yml) CI workflow, and are smoke-tested by importing the `roboplan` namespace and every compiled submodule. +That workflow runs on pull requests so wheel build breakage is caught before release. +The [`release.yml`](../../.github/workflows/release.yml) workflow invokes the same build workflow from the tagged commit, downloads those fresh artifacts, and publishes them through PyPI trusted publishing. ## Local checks -Run commands from the repository root. Keep native parallelism capped on small -machines, matching CI: +Run commands from the repository root. Keep native parallelism capped on small machines, matching CI: ```bash export CMAKE_BUILD_PARALLEL_LEVEL=2 @@ -35,8 +29,7 @@ Source-build and import-test the unified wheel path: ```bash uv venv --seed /tmp/roboplan-wheel-check -uv pip install --python /tmp/roboplan-wheel-check/bin/python --no-cache . -cd /tmp +uv pip install -v --python /tmp/roboplan-wheel-check/bin/python --no-cache . /tmp/roboplan-wheel-check/bin/python - <<'PY' import roboplan import roboplan.core @@ -56,3 +49,20 @@ Check the structural packaging contract: ```bash uvx pytest tests/unified_python_package_test.py -q ``` + +## Run an example against the wheel + +To prove the wheel is usable end-to-end, install it into a fresh environment and run one of the examples. +The examples are not part of the wheel, so they pull in a few extra runtime dependencies on top of `roboplan` itself. + +```bash +# 1. Create an isolated environment and install the wheel we just built. +uv venv --seed /tmp/roboplan-wheel-check +uv pip install --python /tmp/roboplan-wheel-check/bin/python --no-cache . + +# 2. Install the extra dependencies the examples need. +uv pip install --python /tmp/roboplan-wheel-check/bin/python pycollada tyro xacro viser + +# 3. Run an example. +/tmp/roboplan-wheel-check/bin/python roboplan_examples/python/example_ik.py +``` diff --git a/pyproject.toml b/pyproject.toml index 39952675..cd8cfc23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,8 @@ readme = "README.md" requires-python = ">=3.10,<3.15" authors = [ { name = "Sebastian Castro", email = "sebas.a.castro@gmail.com" }, - { name = "JafarAbdi", email = "jafar.uruc@gmail.com" }, + { name = "Erik Holum", email = "eholum@gmail.com" }, + { name = "Sebastian Jahr", email = "sebastian.jahr@tuta.io" }, ] classifiers = [ "License :: OSI Approved :: MIT License", diff --git a/tests/unified_python_package_test.py b/tests/unified_python_package_test.py index 543ddc4c..2c46ef10 100644 --- a/tests/unified_python_package_test.py +++ b/tests/unified_python_package_test.py @@ -117,8 +117,6 @@ def test_root_cmake_superbuild_adds_all_python_binding_packages_in_order() -> No source, [ "SKBUILD_PROJECT_VERSION", - "SKBUILD_PROJECT_VERSION_FULL", - "ROBOPLAN_PYTHON_VERSION", "roboplan_configure_scikit_build_prefix()", "roboplan_configure_unified_python_wheel()", ], @@ -188,8 +186,7 @@ def test_binding_packages_keep_upstream_nanobind_discovery() -> None: assert "-m nanobind --cmake_dir" in source, path assert "find_package(nanobind CONFIG REQUIRED)" in source, path - assert "ROBOPLAN_PYTHON_VERSION" in source, path - assert 'ROBOPLAN_VERSION="${ROBOPLAN_PYTHON_VERSION}"' in source, path + assert 'ROBOPLAN_VERSION="${PROJECT_VERSION}"' in source, path assert "ROBOPLAN_NANOBIND_PYTHON_RESULT" not in source, path