From 23987bb653ecfa6d2dc8ea5af368e273b9cc8d17 Mon Sep 17 00:00:00 2001 From: OpenHands Date: Fri, 14 Aug 2026 23:40:31 +0000 Subject: [PATCH 1/8] Add nested src/CMakeLists.txt with sas_core_pure library + fix Docker compose for container environments - Create src/CMakeLists.txt that builds all non-ROS2 C++ sources into a static library target `sas_core_pure` with -fPIC. - Refactor parent CMakeLists.txt: delegate source compilation to add_subdirectory(src) and keep an INTERFACE alias `sas_core` so external ROS packages can still link against the original target name. - Remove the bind-mount from docker/compose.yml and COPY the source into the Dockerfile instead, since Docker Desktop cannot access container internal paths. - All 6 ROS2 examples (C++ and Python) build and run successfully. --- CMakeLists.txt | 83 ++++++++++++++-------------------------------- docker/Dockerfile | 3 +- docker/compose.yml | 6 ++-- src/CMakeLists.txt | 29 ++++++++++++++++ 4 files changed, 58 insertions(+), 63 deletions(-) create mode 100644 src/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index dbe631d..276e2e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,116 +18,84 @@ add_subdirectory(pybind11) find_package(ament_cmake REQUIRED) find_package(Eigen3 REQUIRED) -##### CPP LIBRARY ##### - -add_library(${PROJECT_NAME} SHARED - src/sas_clock.cpp - src/sas_core.cpp - src/sas_object.cpp - src/sas_shutdown_signaler.cpp - # sas_robot_driver - src/sas_robot_driver.cpp - src/examples/sas_robot_driver_example.cpp - src/eigen3_std_conversions.cpp - ) +############################## +# Pure C++ library (src/) # +############################## +add_subdirectory(src) -ament_target_dependencies(${PROJECT_NAME} Eigen3) - -target_include_directories(${PROJECT_NAME} - PUBLIC - $ - $) +# INTERFACE alias so external ROS packages can still link against `sas_core`. +# All symbols come from sas_core_pure transitively. +add_library(sas_core INTERFACE) +target_link_libraries(sas_core INTERFACE sas_core_pure) ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) ament_export_dependencies(Eigen3) -target_link_libraries(${PROJECT_NAME} - -ldqrobotics - Eigen3::Eigen - ) - install( DIRECTORY include/ DESTINATION include ) install( - TARGETS ${PROJECT_NAME} + TARGETS sas_core sas_core_pure EXPORT export_${PROJECT_NAME} - LIBRARY DESTINATION lib - #ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib INCLUDES DESTINATION include ) -##END## CPP LIBRARY ##### - -##### CPP BINARY ##### - +############################## +# Binaries # +############################## add_executable(sas_core_example src/examples/sas_core_example.cpp) target_link_libraries(sas_core_example -ldqrobotics - ${PROJECT_NAME} + sas_core_pure ) install(TARGETS sas_core_example DESTINATION lib/${PROJECT_NAME}) -##END## CPP BINARY ##### - -##### CPP BINARY ##### - add_executable(sas_clock_example src/examples/sas_clock_example.cpp) target_link_libraries(sas_clock_example - ${PROJECT_NAME} + sas_core_pure ) install(TARGETS sas_clock_example DESTINATION lib/${PROJECT_NAME}) -##END## CPP BINARY ##### - -##### CPP BINARY ##### - add_executable(sas_clock_sched_fifo_example src/examples/sas_clock_sched_fifo_example.cpp) target_link_libraries(sas_clock_sched_fifo_example - ${PROJECT_NAME} + sas_core_pure ) install(TARGETS sas_clock_sched_fifo_example DESTINATION lib/${PROJECT_NAME}) -##END## CPP BINARY ##### - -##### CPP BINARY ##### - add_executable(sas_robot_driver_example src/examples/sas_robot_driver_example_main.cpp) target_link_libraries(sas_robot_driver_example - ${PROJECT_NAME} + sas_core_pure ) install(TARGETS sas_robot_driver_example DESTINATION lib/${PROJECT_NAME}) -##END## CPP BINARY ##### - -##### PYBIND11 LIBRARY ##### - +############################## +# Pybind11 # +############################## ament_python_install_package(${PROJECT_NAME}) - pybind11_add_module(_${PROJECT_NAME} SHARED src/sas_core_py.cpp src/sas_robot_driver_py.cpp @@ -139,23 +107,20 @@ target_include_directories(_${PROJECT_NAME} $) target_compile_definitions(_${PROJECT_NAME} PRIVATE IS_SAS_PYTHON_BUILD) -# https://github.com/pybind/pybind11/issues/387 -target_link_libraries(_${PROJECT_NAME} PRIVATE ${PROJECT_NAME} -ldqrobotics) +target_link_libraries(_${PROJECT_NAME} PRIVATE sas_core_pure -ldqrobotics) install(TARGETS _${PROJECT_NAME} DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}" ) -##END## PYBIND11 LIBRARY ##### - -# According to https://github.com/SmartArmStack/sas_datalogger/blob/78ac681f9cb049c5b9715d215f163565f16b5cdc/CMakeLists.txt -##### PYTHON EXECUTABLES ##### +############################## +# Python executables # +############################## install(PROGRAMS scripts/sas_clock_example_py.py scripts/sas_robot_driver_subclass_example_py.py scripts/sas_clock_sched_fifo_example_py.py DESTINATION lib/${PROJECT_NAME} ) -##END## PYTHON EXECUTABLES ##### ament_package() diff --git a/docker/Dockerfile b/docker/Dockerfile index bacfc61..6cd2542 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,4 +6,5 @@ ENV BASH_ENV="/etc/bash_env" RUN sudo apt-get update && sudo apt-get upgrade -y RUN python3 -m pip install --upgrade dqrobotics --break-system-packages RUN sudo sudo apt-get remove -y ros-jazzy-sas-core -RUN mkdir -p /root/sas_core_devel/src/ \ No newline at end of file +RUN mkdir -p /root/sas_core_devel/src/ +COPY . /root/sas_core_devel/src/sas_core \ No newline at end of file diff --git a/docker/compose.yml b/docker/compose.yml index e2e485a..ea4e18c 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -1,8 +1,8 @@ services: sas_core: - build: . - volumes: - - ../../sas_core:/root/sas_core_devel/src/sas_core + build: + context: .. + dockerfile: docker/Dockerfile environment: PYTHONUNBUFFERED: 1 command: /bin/bash -c " diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..d69bc16 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,29 @@ +# sas_core_pure: the pure C++ library (no ROS2/ament dependencies) + +find_package(Eigen3 REQUIRED) + +add_library(sas_core_pure STATIC + sas_clock.cpp + sas_core.cpp + sas_object.cpp + sas_shutdown_signaler.cpp + sas_robot_driver.cpp + examples/sas_robot_driver_example.cpp + eigen3_std_conversions.cpp +) + +set_target_properties(sas_core_pure PROPERTIES + POSITION_INDEPENDENT_CODE ON +) + +target_include_directories(sas_core_pure + PUBLIC + $ + $ +) + +target_link_libraries(sas_core_pure + PUBLIC + -ldqrobotics + Eigen3::Eigen +) \ No newline at end of file From 089086655bc44cc1bf67d6b4ddecee90e4a39761 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 15 Aug 2026 08:40:18 +0000 Subject: [PATCH 2/8] Move sas_core_pure CMake to cmake/sas_core_pure.cmake and include via parent CMakeLists.txt - Relocate src/CMakeLists.txt to cmake/sas_core_pure.cmake - Switch from add_subdirectory(src) to include(cmake/sas_core_pure.cmake) - Use CMAKE_CURRENT_LIST_DIR for correct path resolution in included file - Update test_consumer to link against sas_core::sas_core_pure target - Fix Docker COPY to include full build context --- CMakeLists.txt | 8 +++++-- cmake/sas_core_pure.cmake | 37 +++++++++++++++++++++++++++++ docker/Dockerfile | 3 ++- docker/compose.yml | 1 + src/CMakeLists.txt | 29 ---------------------- test_consumer/CMakeLists.txt | 15 ++++++++++++ test_consumer/package.xml | 20 ++++++++++++++++ test_consumer/src/test_consumer.cpp | 24 +++++++++++++++++++ 8 files changed, 105 insertions(+), 32 deletions(-) create mode 100644 cmake/sas_core_pure.cmake delete mode 100644 src/CMakeLists.txt create mode 100644 test_consumer/CMakeLists.txt create mode 100644 test_consumer/package.xml create mode 100644 test_consumer/src/test_consumer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 276e2e1..86b7f71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,17 +19,21 @@ find_package(ament_cmake REQUIRED) find_package(Eigen3 REQUIRED) ############################## -# Pure C++ library (src/) # +# Pure C++ library # ############################## -add_subdirectory(src) +include(cmake/sas_core_pure.cmake) # INTERFACE alias so external ROS packages can still link against `sas_core`. # All symbols come from sas_core_pure transitively. add_library(sas_core INTERFACE) target_link_libraries(sas_core INTERFACE sas_core_pure) +target_include_directories(sas_core INTERFACE + $ + $) ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) ament_export_dependencies(Eigen3) +ament_export_include_directories(include) install( DIRECTORY include/ diff --git a/cmake/sas_core_pure.cmake b/cmake/sas_core_pure.cmake new file mode 100644 index 0000000..fb5a7c8 --- /dev/null +++ b/cmake/sas_core_pure.cmake @@ -0,0 +1,37 @@ +# sas_core_pure: the pure C++ library (no ROS2/ament dependencies) + +find_package(Eigen3 REQUIRED) + +# Use CURRENT_LIST_DIR so paths resolve relative to this cmake file +set(_sas_core_pure_dir "${CMAKE_CURRENT_LIST_DIR}/../src") + +set(_sas_core_pure_sources + ${_sas_core_pure_dir}/sas_clock.cpp + ${_sas_core_pure_dir}/sas_core.cpp + ${_sas_core_pure_dir}/sas_object.cpp + ${_sas_core_pure_dir}/sas_shutdown_signaler.cpp + ${_sas_core_pure_dir}/sas_robot_driver.cpp + ${_sas_core_pure_dir}/examples/sas_robot_driver_example.cpp + ${_sas_core_pure_dir}/eigen3_std_conversions.cpp +) + +add_library(sas_core_pure STATIC ${_sas_core_pure_sources}) + +set_target_properties(sas_core_pure PROPERTIES + POSITION_INDEPENDENT_CODE ON +) + +target_include_directories(sas_core_pure + PUBLIC + $ + $ +) + +target_link_libraries(sas_core_pure + PUBLIC + -ldqrobotics + Eigen3::Eigen +) + +unset(_sas_core_pure_dir) +unset(_sas_core_pure_sources) \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 6cd2542..f713016 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -7,4 +7,5 @@ RUN sudo apt-get update && sudo apt-get upgrade -y RUN python3 -m pip install --upgrade dqrobotics --break-system-packages RUN sudo sudo apt-get remove -y ros-jazzy-sas-core RUN mkdir -p /root/sas_core_devel/src/ -COPY . /root/sas_core_devel/src/sas_core \ No newline at end of file +COPY . /root/sas_core_devel/src/sas_core +COPY test_consumer /root/sas_core_devel/src/test_consumer \ No newline at end of file diff --git a/docker/compose.yml b/docker/compose.yml index ea4e18c..cebf367 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -16,4 +16,5 @@ services: && ros2 run sas_core sas_robot_driver_subclass_example_py.py && ros2 run sas_core sas_clock_sched_fifo_example && ros2 run sas_core sas_clock_sched_fifo_example_py.py + && ros2 run test_consumer test_consumer " \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index d69bc16..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -# sas_core_pure: the pure C++ library (no ROS2/ament dependencies) - -find_package(Eigen3 REQUIRED) - -add_library(sas_core_pure STATIC - sas_clock.cpp - sas_core.cpp - sas_object.cpp - sas_shutdown_signaler.cpp - sas_robot_driver.cpp - examples/sas_robot_driver_example.cpp - eigen3_std_conversions.cpp -) - -set_target_properties(sas_core_pure PROPERTIES - POSITION_INDEPENDENT_CODE ON -) - -target_include_directories(sas_core_pure - PUBLIC - $ - $ -) - -target_link_libraries(sas_core_pure - PUBLIC - -ldqrobotics - Eigen3::Eigen -) \ No newline at end of file diff --git a/test_consumer/CMakeLists.txt b/test_consumer/CMakeLists.txt new file mode 100644 index 0000000..7566e42 --- /dev/null +++ b/test_consumer/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.8) +project(test_consumer) + +find_package(ament_cmake REQUIRED) +find_package(sas_core REQUIRED) + +# Build a simple executable that links against the sas_core library +add_executable(test_consumer src/test_consumer.cpp) +target_link_libraries(test_consumer sas_core::sas_core_pure) + +install(TARGETS + test_consumer + DESTINATION lib/${PROJECT_NAME}) + +ament_package() \ No newline at end of file diff --git a/test_consumer/package.xml b/test_consumer/package.xml new file mode 100644 index 0000000..50cda4b --- /dev/null +++ b/test_consumer/package.xml @@ -0,0 +1,20 @@ + + + + test_consumer + 0.0.0 + Temporary package to test importing sas_core from another ROS2 package + test + LGPLv3 + + ament_cmake + sas_core + sas_core + + ament_lint_auto + ament_lint_common + + + ament_cmake + + \ No newline at end of file diff --git a/test_consumer/src/test_consumer.cpp b/test_consumer/src/test_consumer.cpp new file mode 100644 index 0000000..81f1fbf --- /dev/null +++ b/test_consumer/src/test_consumer.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + // Test sas_core namespace is accessible and usable + VectorXd a(2); + a << 1.0, 2.0; + + VectorXd b(2); + b << 3.0, 4.0; + + VectorXd c = sas::concatenate(a, b); + std::cout << "concatenate result: " << c.transpose() << std::endl; + + // Test incremental_mean + double mean = sas::incremental_mean(1.0, 1, 3.0); + std::cout << "incremental_mean(1.0, 1, 3.0) = " << mean << std::endl; + + std::cout << "sas_core imported successfully from test_consumer!" << std::endl; + return 0; +} \ No newline at end of file From ff098c032943928cef57ed282be41dee52b86e64 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 15 Aug 2026 10:33:46 +0000 Subject: [PATCH 3/8] Rename cpplib.cmake, extract pythonlib.cmake, add ROS2_BUILD option - Rename cmake/sas_core_pure.cmake to cmake/cpplib.cmake - Extract Python pybind11 wrapper to cmake/pythonlib.cmake - Add ROS2_BUILD option (ON by default) in main CMakeLists.txt - When ROS2_BUILD=OFF: only builds cpplib + pythonlib targets - When ROS2_BUILD=ON: full ament build with examples and exports - Python module install path adapts to ROS2 (ament) or plain CMake context --- CMakeLists.txt | 172 ++++++++------------ cmake/{sas_core_pure.cmake => cpplib.cmake} | 0 cmake/pythonlib.cmake | 34 ++++ 3 files changed, 98 insertions(+), 108 deletions(-) rename cmake/{sas_core_pure.cmake => cpplib.cmake} (100%) create mode 100644 cmake/pythonlib.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 86b7f71..06c90c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,130 +1,86 @@ cmake_minimum_required(VERSION 3.8) project(sas_core) +option(ROS2_BUILD "Enable ROS2/ament build (examples + ament integration)" ON) + if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -################################# -# pybind11 import block [BEGIN] # -# vvvvvvvvvvvvvvvvvvvvvvvvvvvvv # -set(PYBIND11_FINDPYTHON ON) # Fix CMP0148 Warning. https://github.com/pybind/pybind11/issues/4785 -add_subdirectory(pybind11) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ # -# pybind11 import block [END] # -############################### - -# find dependencies -find_package(ament_cmake REQUIRED) +# Find non-ROS dependencies find_package(Eigen3 REQUIRED) ############################## # Pure C++ library # ############################## -include(cmake/sas_core_pure.cmake) - -# INTERFACE alias so external ROS packages can still link against `sas_core`. -# All symbols come from sas_core_pure transitively. -add_library(sas_core INTERFACE) -target_link_libraries(sas_core INTERFACE sas_core_pure) -target_include_directories(sas_core INTERFACE - $ - $) - -ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) -ament_export_dependencies(Eigen3) -ament_export_include_directories(include) - -install( - DIRECTORY include/ - DESTINATION include - ) - -install( - TARGETS sas_core sas_core_pure - EXPORT export_${PROJECT_NAME} - ARCHIVE DESTINATION lib - INCLUDES DESTINATION include - ) +include(cmake/cpplib.cmake) ############################## -# Binaries # +# Python wrapper (pybind11) # ############################## -add_executable(sas_core_example - src/examples/sas_core_example.cpp) - -target_link_libraries(sas_core_example - -ldqrobotics - sas_core_pure - ) - -install(TARGETS - sas_core_example - DESTINATION lib/${PROJECT_NAME}) - -add_executable(sas_clock_example - src/examples/sas_clock_example.cpp) - -target_link_libraries(sas_clock_example - sas_core_pure - ) - -install(TARGETS - sas_clock_example - DESTINATION lib/${PROJECT_NAME}) - -add_executable(sas_clock_sched_fifo_example - src/examples/sas_clock_sched_fifo_example.cpp) - -target_link_libraries(sas_clock_sched_fifo_example - sas_core_pure - ) - -install(TARGETS - sas_clock_sched_fifo_example - DESTINATION lib/${PROJECT_NAME}) - -add_executable(sas_robot_driver_example - src/examples/sas_robot_driver_example_main.cpp) - -target_link_libraries(sas_robot_driver_example - sas_core_pure - ) - -install(TARGETS - sas_robot_driver_example - DESTINATION lib/${PROJECT_NAME}) +if(ROS2_BUILD) + find_package(ament_cmake REQUIRED) + ament_python_install_package(${PROJECT_NAME}) + # ament_python_install_package sets PYTHON_INSTALL_DIR — pass it to pythonlib.cmake + set(_SAS_PYTHON_INSTALL_DIR "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}") +endif() +include(cmake/pythonlib.cmake) ############################## -# Pybind11 # +# ROS2 / ament integration # ############################## -ament_python_install_package(${PROJECT_NAME}) - -pybind11_add_module(_${PROJECT_NAME} SHARED - src/sas_core_py.cpp - src/sas_robot_driver_py.cpp +if(ROS2_BUILD) + # INTERFACE alias so external ROS packages can link against `sas_core`. + add_library(sas_core INTERFACE) + target_link_libraries(sas_core INTERFACE sas_core_pure) + target_include_directories(sas_core INTERFACE + $ + $) + + ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) + ament_export_dependencies(Eigen3) + ament_export_include_directories(include) + + install( + DIRECTORY include/ + DESTINATION include ) -target_include_directories(_${PROJECT_NAME} - PUBLIC - $ - $) - -target_compile_definitions(_${PROJECT_NAME} PRIVATE IS_SAS_PYTHON_BUILD) -target_link_libraries(_${PROJECT_NAME} PRIVATE sas_core_pure -ldqrobotics) - -install(TARGETS _${PROJECT_NAME} - DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}" -) + install( + TARGETS sas_core sas_core_pure + EXPORT export_${PROJECT_NAME} + ARCHIVE DESTINATION lib + INCLUDES DESTINATION include + ) -############################## -# Python executables # -############################## -install(PROGRAMS - scripts/sas_clock_example_py.py - scripts/sas_robot_driver_subclass_example_py.py - scripts/sas_clock_sched_fifo_example_py.py - DESTINATION lib/${PROJECT_NAME} -) + ################################################################################ + # Example executables + ################################################################################ + add_executable(sas_core_example src/examples/sas_core_example.cpp) + target_link_libraries(sas_core_example -ldqrobotics sas_core_pure) + install(TARGETS sas_core_example DESTINATION lib/${PROJECT_NAME}) + + add_executable(sas_clock_example src/examples/sas_clock_example.cpp) + target_link_libraries(sas_clock_example sas_core_pure) + install(TARGETS sas_clock_example DESTINATION lib/${PROJECT_NAME}) + + add_executable(sas_clock_sched_fifo_example src/examples/sas_clock_sched_fifo_example.cpp) + target_link_libraries(sas_clock_sched_fifo_example sas_core_pure) + install(TARGETS sas_clock_sched_fifo_example DESTINATION lib/${PROJECT_NAME}) + + add_executable(sas_robot_driver_example src/examples/sas_robot_driver_example_main.cpp) + target_link_libraries(sas_robot_driver_example sas_core_pure) + install(TARGETS sas_robot_driver_example DESTINATION lib/${PROJECT_NAME}) + + ################################################################################ + # Python executables + ################################################################################ + install(PROGRAMS + scripts/sas_clock_example_py.py + scripts/sas_robot_driver_subclass_example_py.py + scripts/sas_clock_sched_fifo_example_py.py + DESTINATION lib/${PROJECT_NAME} + ) -ament_package() + ament_package() +endif() diff --git a/cmake/sas_core_pure.cmake b/cmake/cpplib.cmake similarity index 100% rename from cmake/sas_core_pure.cmake rename to cmake/cpplib.cmake diff --git a/cmake/pythonlib.cmake b/cmake/pythonlib.cmake new file mode 100644 index 0000000..70be815 --- /dev/null +++ b/cmake/pythonlib.cmake @@ -0,0 +1,34 @@ +# Python wrapper module via pybind11 + +find_package(Python3 REQUIRED COMPONENTS Development) + +# pybind11 import block [BEGIN] +# vvvvvvvvvvvvvvvvvvvvvvvvvvvvv # +set(PYBIND11_FINDPYTHON ON) # Fix CMP0148 Warning. https://github.com/pybind/pybind11/issues/4785 +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../pybind11) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ # +# pybind11 import block [END] # + +pybind11_add_module(_${PROJECT_NAME} SHARED + ${CMAKE_CURRENT_LIST_DIR}/../src/sas_core_py.cpp + ${CMAKE_CURRENT_LIST_DIR}/../src/sas_robot_driver_py.cpp +) + +target_include_directories(_${PROJECT_NAME} + PUBLIC + $ + $) + +target_compile_definitions(_${PROJECT_NAME} PRIVATE IS_SAS_PYTHON_BUILD) +target_link_libraries(_${PROJECT_NAME} PRIVATE sas_core_pure -ldqrobotics) + +# Install path: use _SAS_PYTHON_INSTALL_DIR if set by parent (ROS2/ament), +# otherwise default to the standard Python site-packages location. +if(DEFINED _SAS_PYTHON_INSTALL_DIR) + set(_SAS_PY_INSTALL_DEST "${_SAS_PYTHON_INSTALL_DIR}") +else() + set(_SAS_PY_INSTALL_DEST "lib/python3/dist-packages/${PROJECT_NAME}") +endif() + +install(TARGETS _${PROJECT_NAME} + DESTINATION "${_SAS_PY_INSTALL_DEST}") \ No newline at end of file From d39585fc2051b1aa44b7a74a4ac34d8411794980 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 15 Aug 2026 11:10:42 +0000 Subject: [PATCH 4/8] Add Python test to test_consumer and standalone non-ROS2 CMake test package - Add test_consumer/test/test_python_import.py: verifies sas_core Python bindings (Clock, Statistics, RobotDriver, ShutdownSignaler) import correctly from a downstream ROS2 package - Create standalone_consumer/: temporary plain-CMake package (no ROS2/ament) that includes cmake/cpplib.cmake and cmake/pythonlib.cmake to validate sas_core can be consumed outside of ROS2 - standalone_consumer/src/standalone_consumer.cpp: links sas_core_pure and exercises sas::concatenate, sas::incremental_mean, ShutdownSignaler - standalone_consumer/test/test_python_import.py: validates Python bindings from the non-ROS2 build - Fix pythonlib.cmake to always emit _sas_core module (not project-name dependent) so the import works regardless of consuming project - Fix pythonlib.cmake add_subdirectory to provide binary dir for out-of-tree includes - Update Dockerfile to copy standalone_consumer outside colcon workspace - Update compose.yml to build standalone_consumer with plain cmake and run both Python import tests --- cmake/pythonlib.cmake | 22 +++++++----- docker/Dockerfile | 4 ++- docker/compose.yml | 11 ++++++ standalone_consumer/CMakeLists.txt | 34 +++++++++++++++++++ .../src/standalone_consumer.cpp | 32 +++++++++++++++++ .../test/test_python_import.py | 17 ++++++++++ test_consumer/CMakeLists.txt | 12 +++++++ test_consumer/test/test_python_import.py | 21 ++++++++++++ 8 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 standalone_consumer/CMakeLists.txt create mode 100644 standalone_consumer/src/standalone_consumer.cpp create mode 100644 standalone_consumer/test/test_python_import.py create mode 100644 test_consumer/test/test_python_import.py diff --git a/cmake/pythonlib.cmake b/cmake/pythonlib.cmake index 70be815..3ec24ee 100644 --- a/cmake/pythonlib.cmake +++ b/cmake/pythonlib.cmake @@ -5,30 +5,36 @@ find_package(Python3 REQUIRED COMPONENTS Development) # pybind11 import block [BEGIN] # vvvvvvvvvvvvvvvvvvvvvvvvvvvvv # set(PYBIND11_FINDPYTHON ON) # Fix CMP0148 Warning. https://github.com/pybind/pybind11/issues/4785 -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../pybind11) +# Provide binary directory for out-of-tree include (e.g. when included from standalone_consumer) +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../pybind11 ${CMAKE_CURRENT_BINARY_DIR}/pybind11) # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # pybind11 import block [END] # -pybind11_add_module(_${PROJECT_NAME} SHARED +# Always build the module as _sas_core regardless of the including project's name +set(_SAS_PY_MODULE_NAME "_sas_core") + +pybind11_add_module(${_SAS_PY_MODULE_NAME} SHARED ${CMAKE_CURRENT_LIST_DIR}/../src/sas_core_py.cpp ${CMAKE_CURRENT_LIST_DIR}/../src/sas_robot_driver_py.cpp ) -target_include_directories(_${PROJECT_NAME} +target_include_directories(${_SAS_PY_MODULE_NAME} PUBLIC $ $) -target_compile_definitions(_${PROJECT_NAME} PRIVATE IS_SAS_PYTHON_BUILD) -target_link_libraries(_${PROJECT_NAME} PRIVATE sas_core_pure -ldqrobotics) +target_compile_definitions(${_SAS_PY_MODULE_NAME} PRIVATE IS_SAS_PYTHON_BUILD) +target_link_libraries(${_SAS_PY_MODULE_NAME} PRIVATE sas_core_pure -ldqrobotics) # Install path: use _SAS_PYTHON_INSTALL_DIR if set by parent (ROS2/ament), # otherwise default to the standard Python site-packages location. if(DEFINED _SAS_PYTHON_INSTALL_DIR) set(_SAS_PY_INSTALL_DEST "${_SAS_PYTHON_INSTALL_DIR}") else() - set(_SAS_PY_INSTALL_DEST "lib/python3/dist-packages/${PROJECT_NAME}") + set(_SAS_PY_INSTALL_DEST "lib/python3/dist-packages/sas_core") endif() -install(TARGETS _${PROJECT_NAME} - DESTINATION "${_SAS_PY_INSTALL_DEST}") \ No newline at end of file +install(TARGETS ${_SAS_PY_MODULE_NAME} + DESTINATION "${_SAS_PY_INSTALL_DEST}") + +unset(_SAS_PY_MODULE_NAME) \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index f713016..6976156 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,4 +8,6 @@ RUN python3 -m pip install --upgrade dqrobotics --break-system-packages RUN sudo sudo apt-get remove -y ros-jazzy-sas-core RUN mkdir -p /root/sas_core_devel/src/ COPY . /root/sas_core_devel/src/sas_core -COPY test_consumer /root/sas_core_devel/src/test_consumer \ No newline at end of file +COPY test_consumer /root/sas_core_devel/src/test_consumer +# standalone_consumer lives outside the colcon workspace +COPY standalone_consumer /root/standalone_consumer \ No newline at end of file diff --git a/docker/compose.yml b/docker/compose.yml index cebf367..b8eaa74 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -17,4 +17,15 @@ services: && ros2 run sas_core sas_clock_sched_fifo_example && ros2 run sas_core sas_clock_sched_fifo_example_py.py && ros2 run test_consumer test_consumer + && echo '--- Running Python import test from test_consumer ---' + && python3 test_consumer/test/test_python_import.py + && echo '--- Building standalone_consumer (no ROS2) ---' + && mkdir -p /root/standalone_build + && cd /root/standalone_build + && cmake /root/standalone_consumer -DCMAKE_INSTALL_PREFIX=/root/standalone_install + && make + && /root/standalone_build/standalone_consumer + && echo '--- Running Python import test from standalone_consumer ---' + && cp /root/standalone_build/_sas_core.*.so /root/sas_core_devel/src/sas_core/sas_core/ 2>/dev/null || true + && PYTHONPATH=/root/sas_core_devel/src/sas_core python3 /root/standalone_consumer/test/test_python_import.py " \ No newline at end of file diff --git a/standalone_consumer/CMakeLists.txt b/standalone_consumer/CMakeLists.txt new file mode 100644 index 0000000..18fba8a --- /dev/null +++ b/standalone_consumer/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.8) +project(standalone_consumer) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(Eigen3 REQUIRED) + +# Path to the sas_core source tree — required to include cmake modules +set(SAS_CORE_SOURCE_DIR "/root/sas_core_devel/src/sas_core" CACHE PATH "Path to sas_core source") + +# Include sas_core pure C++ and Python library definitions (no ROS2/ament needed) +include(${SAS_CORE_SOURCE_DIR}/cmake/cpplib.cmake) +include(${SAS_CORE_SOURCE_DIR}/cmake/pythonlib.cmake) + +############################## +# Standalone C++ consumer # +############################## +add_executable(standalone_consumer src/standalone_consumer.cpp) +target_link_libraries(standalone_consumer sas_core_pure) + +############################## +# Standalone Python test # +############################## +# The Python test runs as a CTest; it requires the built _sas_core module. +find_package(Python3 REQUIRED COMPONENTS Interpreter) +add_test( + NAME test_python_import + COMMAND ${Python3_EXECUTABLE} test/test_python_import.py +) +set_tests_properties(test_python_import PROPERTIES + ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:${CMAKE_SOURCE_DIR}/.." +) \ No newline at end of file diff --git a/standalone_consumer/src/standalone_consumer.cpp b/standalone_consumer/src/standalone_consumer.cpp new file mode 100644 index 0000000..1301f7a --- /dev/null +++ b/standalone_consumer/src/standalone_consumer.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + std::cout << "=== Standalone C++ consumer (no ROS2) ===" << std::endl; + + // Test sas_core namespace is accessible and usable + VectorXd a(2); + a << 1.0, 2.0; + + VectorXd b(2); + b << 3.0, 4.0; + + VectorXd c = sas::concatenate(a, b); + std::cout << "concatenate result: " << c.transpose() << std::endl; + + // Test incremental_mean + double mean = sas::incremental_mean(1.0, 1, 3.0); + std::cout << "incremental_mean(1.0, 1, 3.0) = " << mean << std::endl; + + // Test ShutdownSignaler + sas::ShutdownSignaler signaler; + signaler.shutdown(); + bool stopped = signaler.should_shutdown(); + std::cout << "should_shutdown() = " << stopped << std::endl; + + std::cout << "sas_core_pure imported successfully from standalone_consumer (no ROS2)!" << std::endl; + return 0; +} \ No newline at end of file diff --git a/standalone_consumer/test/test_python_import.py b/standalone_consumer/test/test_python_import.py new file mode 100644 index 0000000..212ef99 --- /dev/null +++ b/standalone_consumer/test/test_python_import.py @@ -0,0 +1,17 @@ +"""Test that sas_core Python bindings can be imported from a standalone (non-ROS2) CMake project.""" + +def test_python_imports(): + from sas_core import Clock, Statistics, RobotDriver, ShutdownSignaler + + clock = Clock(0.1) + assert isinstance(clock, Clock) + assert Statistics is not None + assert RobotDriver is not None + assert ShutdownSignaler is not None + + print("All sas_core Python imports successful from standalone_consumer (no ROS2)!") + return True + + +if __name__ == "__main__": + test_python_imports() \ No newline at end of file diff --git a/test_consumer/CMakeLists.txt b/test_consumer/CMakeLists.txt index 7566e42..70cceb5 100644 --- a/test_consumer/CMakeLists.txt +++ b/test_consumer/CMakeLists.txt @@ -12,4 +12,16 @@ install(TARGETS test_consumer DESTINATION lib/${PROJECT_NAME}) +# Python import test +find_package(Python3 REQUIRED COMPONENTS Interpreter) +if(Python3_FOUND) + add_test( + NAME test_python_import + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_python_import.py + ) + set_tests_properties(test_python_import PROPERTIES + ENVIRONMENT "PYTHONPATH=${CMAKE_INSTALL_PREFIX}/lib/python3.12/site-packages:${CMAKE_INSTALL_PREFIX}/sas_core/lib/python3.12/site-packages" + ) +endif() + ament_package() \ No newline at end of file diff --git a/test_consumer/test/test_python_import.py b/test_consumer/test/test_python_import.py new file mode 100644 index 0000000..a372e99 --- /dev/null +++ b/test_consumer/test/test_python_import.py @@ -0,0 +1,21 @@ +"""Test that sas_core Python bindings can be imported from a downstream ROS2 package.""" + +def test_python_imports(): + """Verify sas_core Python module is importable and usable.""" + from sas_core import Clock, Statistics, RobotDriver, ShutdownSignaler + + # Verify Clock can be instantiated (requires sampling_time argument) + clock = Clock(0.1) + assert isinstance(clock, Clock), "Clock(0.1) should return a Clock instance" + + # Verify Statistics, RobotDriver, ShutdownSignaler are importable + assert Statistics is not None + assert RobotDriver is not None + assert ShutdownSignaler is not None + + print("All sas_core Python imports successful from test_consumer!") + return True + + +if __name__ == "__main__": + test_python_imports() \ No newline at end of file From a7cd70da665c0fcac19d77388d394442d3705159 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 15 Aug 2026 15:46:12 +0000 Subject: [PATCH 5/8] Remove test packages and simplify CMake per best practices - Remove test_consumer/ and standalone_consumer/: temporary validation packages no longer needed in the PR - Simplify cpplib.cmake: use target_sources() instead of inline source list (CMake modern convention), remove redundant temporary variables - Simplify pythonlib.cmake: hardcode module name _sas_core directly, clean up install path logic, remove verbose comment blocks - Simplify root CMakeLists.txt: use foreach(IN ITEMS ...) loop for example executables, remove duplicate find_package(Eigen3) (kept in cpplib.cmake), remove redundant ament_export_include_directories() (target exports already propagate include dirs), clean up section headers - Restore Dockerfile and compose.yml: removed test package references --- CMakeLists.txt | 60 ++++++------------- cmake/cpplib.cmake | 37 ++++-------- cmake/pythonlib.cmake | 35 ++++------- docker/Dockerfile | 5 +- docker/compose.yml | 12 ---- standalone_consumer/CMakeLists.txt | 34 ----------- .../src/standalone_consumer.cpp | 32 ---------- .../test/test_python_import.py | 17 ------ test_consumer/CMakeLists.txt | 27 --------- test_consumer/package.xml | 20 ------- test_consumer/src/test_consumer.cpp | 24 -------- test_consumer/test/test_python_import.py | 21 ------- 12 files changed, 42 insertions(+), 282 deletions(-) delete mode 100644 standalone_consumer/CMakeLists.txt delete mode 100644 standalone_consumer/src/standalone_consumer.cpp delete mode 100644 standalone_consumer/test/test_python_import.py delete mode 100644 test_consumer/CMakeLists.txt delete mode 100644 test_consumer/package.xml delete mode 100644 test_consumer/src/test_consumer.cpp delete mode 100644 test_consumer/test/test_python_import.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 06c90c8..6431729 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,74 +7,50 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -# Find non-ROS dependencies -find_package(Eigen3 REQUIRED) - -############################## -# Pure C++ library # -############################## +# ---- Core libraries (always built) ---- include(cmake/cpplib.cmake) -############################## -# Python wrapper (pybind11) # -############################## if(ROS2_BUILD) find_package(ament_cmake REQUIRED) ament_python_install_package(${PROJECT_NAME}) - # ament_python_install_package sets PYTHON_INSTALL_DIR — pass it to pythonlib.cmake set(_SAS_PYTHON_INSTALL_DIR "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}") endif() + include(cmake/pythonlib.cmake) -############################## -# ROS2 / ament integration # -############################## +# ---- ROS2 / ament integration (conditional) ---- if(ROS2_BUILD) - # INTERFACE alias so external ROS packages can link against `sas_core`. + # INTERFACE alias for downstream ROS packages add_library(sas_core INTERFACE) target_link_libraries(sas_core INTERFACE sas_core_pure) - target_include_directories(sas_core INTERFACE - $ - $) ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) ament_export_dependencies(Eigen3) - ament_export_include_directories(include) - install( - DIRECTORY include/ - DESTINATION include - ) + install(DIRECTORY include/ DESTINATION include) - install( - TARGETS sas_core sas_core_pure + install(TARGETS sas_core sas_core_pure EXPORT export_${PROJECT_NAME} ARCHIVE DESTINATION lib INCLUDES DESTINATION include ) - ################################################################################ # Example executables - ################################################################################ - add_executable(sas_core_example src/examples/sas_core_example.cpp) - target_link_libraries(sas_core_example -ldqrobotics sas_core_pure) - install(TARGETS sas_core_example DESTINATION lib/${PROJECT_NAME}) - - add_executable(sas_clock_example src/examples/sas_clock_example.cpp) - target_link_libraries(sas_clock_example sas_core_pure) - install(TARGETS sas_clock_example DESTINATION lib/${PROJECT_NAME}) - - add_executable(sas_clock_sched_fifo_example src/examples/sas_clock_sched_fifo_example.cpp) - target_link_libraries(sas_clock_sched_fifo_example sas_core_pure) - install(TARGETS sas_clock_sched_fifo_example DESTINATION lib/${PROJECT_NAME}) - - add_executable(sas_robot_driver_example src/examples/sas_robot_driver_example_main.cpp) + foreach(_name IN ITEMS sas_core_example sas_clock_example + sas_clock_sched_fifo_example) + add_executable(${_name} src/examples/${_name}.cpp) + target_link_libraries(${_name} sas_core_pure) + if(_name STREQUAL "sas_core_example") + target_link_libraries(${_name} -ldqrobotics) + endif() + install(TARGETS ${_name} DESTINATION lib/${PROJECT_NAME}) + endforeach() + + add_executable(sas_robot_driver_example + src/examples/sas_robot_driver_example_main.cpp) target_link_libraries(sas_robot_driver_example sas_core_pure) install(TARGETS sas_robot_driver_example DESTINATION lib/${PROJECT_NAME}) - ################################################################################ - # Python executables - ################################################################################ install(PROGRAMS scripts/sas_clock_example_py.py scripts/sas_robot_driver_subclass_example_py.py diff --git a/cmake/cpplib.cmake b/cmake/cpplib.cmake index fb5a7c8..0497114 100644 --- a/cmake/cpplib.cmake +++ b/cmake/cpplib.cmake @@ -1,37 +1,24 @@ -# sas_core_pure: the pure C++ library (no ROS2/ament dependencies) - +# Build the pure C++ library (no ROS2/ament dependencies) find_package(Eigen3 REQUIRED) -# Use CURRENT_LIST_DIR so paths resolve relative to this cmake file -set(_sas_core_pure_dir "${CMAKE_CURRENT_LIST_DIR}/../src") - -set(_sas_core_pure_sources - ${_sas_core_pure_dir}/sas_clock.cpp - ${_sas_core_pure_dir}/sas_core.cpp - ${_sas_core_pure_dir}/sas_object.cpp - ${_sas_core_pure_dir}/sas_shutdown_signaler.cpp - ${_sas_core_pure_dir}/sas_robot_driver.cpp - ${_sas_core_pure_dir}/examples/sas_robot_driver_example.cpp - ${_sas_core_pure_dir}/eigen3_std_conversions.cpp +add_library(sas_core_pure STATIC) +target_sources(sas_core_pure PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../src/sas_clock.cpp + ${CMAKE_CURRENT_LIST_DIR}/../src/sas_core.cpp + ${CMAKE_CURRENT_LIST_DIR}/../src/sas_object.cpp + ${CMAKE_CURRENT_LIST_DIR}/../src/sas_shutdown_signaler.cpp + ${CMAKE_CURRENT_LIST_DIR}/../src/sas_robot_driver.cpp + ${CMAKE_CURRENT_LIST_DIR}/../src/examples/sas_robot_driver_example.cpp + ${CMAKE_CURRENT_LIST_DIR}/../src/eigen3_std_conversions.cpp ) -add_library(sas_core_pure STATIC ${_sas_core_pure_sources}) - set_target_properties(sas_core_pure PROPERTIES POSITION_INDEPENDENT_CODE ON ) -target_include_directories(sas_core_pure - PUBLIC +target_include_directories(sas_core_pure PUBLIC $ $ ) -target_link_libraries(sas_core_pure - PUBLIC - -ldqrobotics - Eigen3::Eigen -) - -unset(_sas_core_pure_dir) -unset(_sas_core_pure_sources) \ No newline at end of file +target_link_libraries(sas_core_pure PUBLIC -ldqrobotics Eigen3::Eigen) \ No newline at end of file diff --git a/cmake/pythonlib.cmake b/cmake/pythonlib.cmake index 3ec24ee..ee1f3dd 100644 --- a/cmake/pythonlib.cmake +++ b/cmake/pythonlib.cmake @@ -1,40 +1,27 @@ # Python wrapper module via pybind11 - find_package(Python3 REQUIRED COMPONENTS Development) -# pybind11 import block [BEGIN] -# vvvvvvvvvvvvvvvvvvvvvvvvvvvvv # -set(PYBIND11_FINDPYTHON ON) # Fix CMP0148 Warning. https://github.com/pybind/pybind11/issues/4785 -# Provide binary directory for out-of-tree include (e.g. when included from standalone_consumer) +set(PYBIND11_FINDPYTHON ON) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../pybind11 ${CMAKE_CURRENT_BINARY_DIR}/pybind11) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ # -# pybind11 import block [END] # - -# Always build the module as _sas_core regardless of the including project's name -set(_SAS_PY_MODULE_NAME "_sas_core") -pybind11_add_module(${_SAS_PY_MODULE_NAME} SHARED +pybind11_add_module(_sas_core SHARED ${CMAKE_CURRENT_LIST_DIR}/../src/sas_core_py.cpp ${CMAKE_CURRENT_LIST_DIR}/../src/sas_robot_driver_py.cpp ) -target_include_directories(${_SAS_PY_MODULE_NAME} - PUBLIC +target_include_directories(_sas_core PUBLIC $ - $) + $ +) -target_compile_definitions(${_SAS_PY_MODULE_NAME} PRIVATE IS_SAS_PYTHON_BUILD) -target_link_libraries(${_SAS_PY_MODULE_NAME} PRIVATE sas_core_pure -ldqrobotics) +target_compile_definitions(_sas_core PRIVATE IS_SAS_PYTHON_BUILD) +target_link_libraries(_sas_core PRIVATE sas_core_pure -ldqrobotics) -# Install path: use _SAS_PYTHON_INSTALL_DIR if set by parent (ROS2/ament), -# otherwise default to the standard Python site-packages location. +# Install path: ament PYTHON_INSTALL_DIR if available, else site-packages if(DEFINED _SAS_PYTHON_INSTALL_DIR) - set(_SAS_PY_INSTALL_DEST "${_SAS_PYTHON_INSTALL_DIR}") + set(_SAS_PY_DEST "${_SAS_PYTHON_INSTALL_DIR}") else() - set(_SAS_PY_INSTALL_DEST "lib/python3/dist-packages/sas_core") + set(_SAS_PY_DEST "lib/python3/dist-packages/sas_core") endif() -install(TARGETS ${_SAS_PY_MODULE_NAME} - DESTINATION "${_SAS_PY_INSTALL_DEST}") - -unset(_SAS_PY_MODULE_NAME) \ No newline at end of file +install(TARGETS _sas_core DESTINATION "${_SAS_PY_DEST}") \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 6976156..6cd2542 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -7,7 +7,4 @@ RUN sudo apt-get update && sudo apt-get upgrade -y RUN python3 -m pip install --upgrade dqrobotics --break-system-packages RUN sudo sudo apt-get remove -y ros-jazzy-sas-core RUN mkdir -p /root/sas_core_devel/src/ -COPY . /root/sas_core_devel/src/sas_core -COPY test_consumer /root/sas_core_devel/src/test_consumer -# standalone_consumer lives outside the colcon workspace -COPY standalone_consumer /root/standalone_consumer \ No newline at end of file +COPY . /root/sas_core_devel/src/sas_core \ No newline at end of file diff --git a/docker/compose.yml b/docker/compose.yml index b8eaa74..ea4e18c 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -16,16 +16,4 @@ services: && ros2 run sas_core sas_robot_driver_subclass_example_py.py && ros2 run sas_core sas_clock_sched_fifo_example && ros2 run sas_core sas_clock_sched_fifo_example_py.py - && ros2 run test_consumer test_consumer - && echo '--- Running Python import test from test_consumer ---' - && python3 test_consumer/test/test_python_import.py - && echo '--- Building standalone_consumer (no ROS2) ---' - && mkdir -p /root/standalone_build - && cd /root/standalone_build - && cmake /root/standalone_consumer -DCMAKE_INSTALL_PREFIX=/root/standalone_install - && make - && /root/standalone_build/standalone_consumer - && echo '--- Running Python import test from standalone_consumer ---' - && cp /root/standalone_build/_sas_core.*.so /root/sas_core_devel/src/sas_core/sas_core/ 2>/dev/null || true - && PYTHONPATH=/root/sas_core_devel/src/sas_core python3 /root/standalone_consumer/test/test_python_import.py " \ No newline at end of file diff --git a/standalone_consumer/CMakeLists.txt b/standalone_consumer/CMakeLists.txt deleted file mode 100644 index 18fba8a..0000000 --- a/standalone_consumer/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -cmake_minimum_required(VERSION 3.8) -project(standalone_consumer) - -if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) -endif() - -find_package(Eigen3 REQUIRED) - -# Path to the sas_core source tree — required to include cmake modules -set(SAS_CORE_SOURCE_DIR "/root/sas_core_devel/src/sas_core" CACHE PATH "Path to sas_core source") - -# Include sas_core pure C++ and Python library definitions (no ROS2/ament needed) -include(${SAS_CORE_SOURCE_DIR}/cmake/cpplib.cmake) -include(${SAS_CORE_SOURCE_DIR}/cmake/pythonlib.cmake) - -############################## -# Standalone C++ consumer # -############################## -add_executable(standalone_consumer src/standalone_consumer.cpp) -target_link_libraries(standalone_consumer sas_core_pure) - -############################## -# Standalone Python test # -############################## -# The Python test runs as a CTest; it requires the built _sas_core module. -find_package(Python3 REQUIRED COMPONENTS Interpreter) -add_test( - NAME test_python_import - COMMAND ${Python3_EXECUTABLE} test/test_python_import.py -) -set_tests_properties(test_python_import PROPERTIES - ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:${CMAKE_SOURCE_DIR}/.." -) \ No newline at end of file diff --git a/standalone_consumer/src/standalone_consumer.cpp b/standalone_consumer/src/standalone_consumer.cpp deleted file mode 100644 index 1301f7a..0000000 --- a/standalone_consumer/src/standalone_consumer.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - std::cout << "=== Standalone C++ consumer (no ROS2) ===" << std::endl; - - // Test sas_core namespace is accessible and usable - VectorXd a(2); - a << 1.0, 2.0; - - VectorXd b(2); - b << 3.0, 4.0; - - VectorXd c = sas::concatenate(a, b); - std::cout << "concatenate result: " << c.transpose() << std::endl; - - // Test incremental_mean - double mean = sas::incremental_mean(1.0, 1, 3.0); - std::cout << "incremental_mean(1.0, 1, 3.0) = " << mean << std::endl; - - // Test ShutdownSignaler - sas::ShutdownSignaler signaler; - signaler.shutdown(); - bool stopped = signaler.should_shutdown(); - std::cout << "should_shutdown() = " << stopped << std::endl; - - std::cout << "sas_core_pure imported successfully from standalone_consumer (no ROS2)!" << std::endl; - return 0; -} \ No newline at end of file diff --git a/standalone_consumer/test/test_python_import.py b/standalone_consumer/test/test_python_import.py deleted file mode 100644 index 212ef99..0000000 --- a/standalone_consumer/test/test_python_import.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Test that sas_core Python bindings can be imported from a standalone (non-ROS2) CMake project.""" - -def test_python_imports(): - from sas_core import Clock, Statistics, RobotDriver, ShutdownSignaler - - clock = Clock(0.1) - assert isinstance(clock, Clock) - assert Statistics is not None - assert RobotDriver is not None - assert ShutdownSignaler is not None - - print("All sas_core Python imports successful from standalone_consumer (no ROS2)!") - return True - - -if __name__ == "__main__": - test_python_imports() \ No newline at end of file diff --git a/test_consumer/CMakeLists.txt b/test_consumer/CMakeLists.txt deleted file mode 100644 index 70cceb5..0000000 --- a/test_consumer/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -cmake_minimum_required(VERSION 3.8) -project(test_consumer) - -find_package(ament_cmake REQUIRED) -find_package(sas_core REQUIRED) - -# Build a simple executable that links against the sas_core library -add_executable(test_consumer src/test_consumer.cpp) -target_link_libraries(test_consumer sas_core::sas_core_pure) - -install(TARGETS - test_consumer - DESTINATION lib/${PROJECT_NAME}) - -# Python import test -find_package(Python3 REQUIRED COMPONENTS Interpreter) -if(Python3_FOUND) - add_test( - NAME test_python_import - COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_python_import.py - ) - set_tests_properties(test_python_import PROPERTIES - ENVIRONMENT "PYTHONPATH=${CMAKE_INSTALL_PREFIX}/lib/python3.12/site-packages:${CMAKE_INSTALL_PREFIX}/sas_core/lib/python3.12/site-packages" - ) -endif() - -ament_package() \ No newline at end of file diff --git a/test_consumer/package.xml b/test_consumer/package.xml deleted file mode 100644 index 50cda4b..0000000 --- a/test_consumer/package.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - test_consumer - 0.0.0 - Temporary package to test importing sas_core from another ROS2 package - test - LGPLv3 - - ament_cmake - sas_core - sas_core - - ament_lint_auto - ament_lint_common - - - ament_cmake - - \ No newline at end of file diff --git a/test_consumer/src/test_consumer.cpp b/test_consumer/src/test_consumer.cpp deleted file mode 100644 index 81f1fbf..0000000 --- a/test_consumer/src/test_consumer.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - // Test sas_core namespace is accessible and usable - VectorXd a(2); - a << 1.0, 2.0; - - VectorXd b(2); - b << 3.0, 4.0; - - VectorXd c = sas::concatenate(a, b); - std::cout << "concatenate result: " << c.transpose() << std::endl; - - // Test incremental_mean - double mean = sas::incremental_mean(1.0, 1, 3.0); - std::cout << "incremental_mean(1.0, 1, 3.0) = " << mean << std::endl; - - std::cout << "sas_core imported successfully from test_consumer!" << std::endl; - return 0; -} \ No newline at end of file diff --git a/test_consumer/test/test_python_import.py b/test_consumer/test/test_python_import.py deleted file mode 100644 index a372e99..0000000 --- a/test_consumer/test/test_python_import.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Test that sas_core Python bindings can be imported from a downstream ROS2 package.""" - -def test_python_imports(): - """Verify sas_core Python module is importable and usable.""" - from sas_core import Clock, Statistics, RobotDriver, ShutdownSignaler - - # Verify Clock can be instantiated (requires sampling_time argument) - clock = Clock(0.1) - assert isinstance(clock, Clock), "Clock(0.1) should return a Clock instance" - - # Verify Statistics, RobotDriver, ShutdownSignaler are importable - assert Statistics is not None - assert RobotDriver is not None - assert ShutdownSignaler is not None - - print("All sas_core Python imports successful from test_consumer!") - return True - - -if __name__ == "__main__": - test_python_imports() \ No newline at end of file From c078206e017a1dcf87c9e91213fa480e144eee00 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 15 Aug 2026 15:52:27 +0000 Subject: [PATCH 6/8] Add FetchContent usage section to README.md --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index d300394..a37bb78 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,29 @@ ros2 run sas_core sas_clock_sched_fifo_example_py.py The `scripts/sas_robot_driver_subclass_example_py.py` file demonstrates how to subclass `sas_core.RobotDriver` in Python and contains a minimal working example. + +## Using as a non-ROS2 dependency (CMake FetchContent) + +To include `sas_core_pure` in a plain CMake project (no ROS2/ament required): + +```cmake +include(FetchContent) +FetchContent_Declare( + sas_core + GIT_REPOSITORY https://github.com/SmartArmStack/sas_core.git + GIT_TAG +) +FetchContent_MakeAvailable(sas_core) + +target_link_libraries(your_target PRIVATE sas_core_pure) +``` + +Pass `-DROS2_BUILD=OFF` when configuring the project if your toolchain does not +provide ament: + +```bash +cmake -B build -DROS2_BUILD=OFF +``` + +The library depends on **Eigen3** and **dqrobotics**; make sure both are +available on your system. From 4658ae40d84b6fa66523beb9e3f37599098687ee Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 15 Aug 2026 15:56:59 +0000 Subject: [PATCH 7/8] Fix FetchContent example: pin jazzy tag and set ROS2_BUILD=OFF inline - Set GIT_TAG to jazzy (the stable branch) - Move ROS2_BUILD=OFF into the CMake example using set(... CACHE BOOL FORCE) so the option is disabled before FetchContent_MakeAvailable, avoiding the need for a separate cmake command-line flag --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a37bb78..fa6dada 100644 --- a/README.md +++ b/README.md @@ -45,18 +45,18 @@ include(FetchContent) FetchContent_Declare( sas_core GIT_REPOSITORY https://github.com/SmartArmStack/sas_core.git - GIT_TAG + GIT_TAG jazzy ) FetchContent_MakeAvailable(sas_core) target_link_libraries(your_target PRIVATE sas_core_pure) ``` -Pass `-DROS2_BUILD=OFF` when configuring the project if your toolchain does not -provide ament: +The `ROS2_BUILD` option defaults to `ON`. Disable it before calling +`FetchContent_MakeAvailable` if your toolchain does not provide ament: -```bash -cmake -B build -DROS2_BUILD=OFF +```cmake +set(ROS2_BUILD OFF CACHE BOOL "" FORCE) ``` The library depends on **Eigen3** and **dqrobotics**; make sure both are From d682aa85e06a65f7cdd7cb106c6a4b4bf0d9c81c Mon Sep 17 00:00:00 2001 From: Murilo M Marinho <46012516+mmmarinho@users.noreply.github.com> Date: Sat, 15 Aug 2026 16:58:56 +0100 Subject: [PATCH 8/8] [README.md] Make it clearer how to import using fetch. --- README.md | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index fa6dada..e549058 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,27 @@ - `scripts/` — example Python scripts. - `src/examples/` — C++ example programs and test nodes. +## Using as a non-ROS2 dependency (CMake FetchContent) + +To include `sas_core_pure` in a plain CMake project (no ROS2/ament required): + +```cmake +include(FetchContent) +FetchContent_Declare( + sas_core + GIT_REPOSITORY https://github.com/SmartArmStack/sas_core.git + GIT_TAG jazzy +) + +set(ROS2_BUILD OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(sas_core) + +target_link_libraries(your_target PRIVATE sas_core_pure) +``` + +The library depends on **Eigen3** and **dqrobotics**; make sure both are +available on your system. + ## Examples Testing on a docker container. @@ -36,28 +57,3 @@ The `scripts/sas_robot_driver_subclass_example_py.py` file demonstrates how to subclass `sas_core.RobotDriver` in Python and contains a minimal working example. -## Using as a non-ROS2 dependency (CMake FetchContent) - -To include `sas_core_pure` in a plain CMake project (no ROS2/ament required): - -```cmake -include(FetchContent) -FetchContent_Declare( - sas_core - GIT_REPOSITORY https://github.com/SmartArmStack/sas_core.git - GIT_TAG jazzy -) -FetchContent_MakeAvailable(sas_core) - -target_link_libraries(your_target PRIVATE sas_core_pure) -``` - -The `ROS2_BUILD` option defaults to `ON`. Disable it before calling -`FetchContent_MakeAvailable` if your toolchain does not provide ament: - -```cmake -set(ROS2_BUILD OFF CACHE BOOL "" FORCE) -``` - -The library depends on **Eigen3** and **dqrobotics**; make sure both are -available on your system.