Skip to content

Commit a0a0f5c

Browse files
Add pycddp Python bindings and CMake install packaging (#160)
* Add a pybind11-based pycddp Python package exposing the full CDDP solver API (dynamics, objectives, constraints, options, solution) * Add CMake install/export rules and a find_package(cddp) config so the C++ library is consumable as an installed package * Remove Matplot++ dependency from the default build and clean up dead plotting code
1 parent a24cfba commit a0a0f5c

49 files changed

Lines changed: 2863 additions & 1135 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
## Summary
2-
<!-- Provide 2-3 bullet points summarizing the key changes and their purpose -->
3-
-
4-
-
2+
<!-- Summarize the change and why it is needed. -->
53
-
64

75
## Changes
8-
<!-- List the specific changes made, grouped by component or functionality -->
9-
- **Component/Feature**: Description of changes
10-
- **Another Component**: Description of changes
6+
<!-- List the concrete code, API, or documentation changes. -->
117
-
128

139
## Test Plan
14-
<!-- Checklist of tests to validate the changes -->
15-
- [ ] Test item 1
16-
- [ ] Test item 2
17-
- [ ] Test item 3
10+
<!-- List the commands run or explain why testing was not performed. -->
11+
- [ ]
1812

19-
## Additional Notes
20-
<!-- Any additional context, design decisions, or concerns for reviewers -->
13+
## Notes
14+
<!-- Add reviewer context only when it is needed. -->
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Python Dependency Guard
2+
3+
on:
4+
pull_request:
5+
branches: [ master ]
6+
paths:
7+
- "pyproject.toml"
8+
- "uv.lock"
9+
- "security/python-direct-deps-allowlist.txt"
10+
- "tools/check_python_dependency_policy.py"
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
policy:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Check out repository
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
24+
- name: Set up Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: "3.12"
28+
29+
- name: Enforce dependency policy
30+
run: python tools/check_python_dependency_policy.py --base-ref "origin/${{ github.base_ref }}"
31+
32+
dependency-review:
33+
runs-on: ubuntu-latest
34+
permissions:
35+
contents: read
36+
pull-requests: write
37+
steps:
38+
- name: Check dependency review
39+
uses: actions/dependency-review-action@v4
40+
with:
41+
fail-on-severity: high

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ pdf_images/
2121

2222
# Solver output
2323
solver_snopt.out
24+
25+
# Python / uv
26+
.venv/
27+
uv.lock
28+
*.egg-info/
29+
__pycache__/
30+
*.so
31+
*.pyd

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

CMakeLists.txt

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ project(
2525
HOMEPAGE_URL "https://github.com/astomodynamics/cddp-cpp"
2626
)
2727

28+
include(GNUInstallDirs)
29+
2830
set(CMAKE_CXX_STANDARD 17) # Enforce C++17 as the minimum standard
2931
set(CMAKE_CXX_STANDARD_REQUIRED ON) # Enforce C++17 as the minimum standard
3032
set(ABSL_PROPAGATE_CXX_STD ON) # Enforce C++17 for absl
@@ -43,6 +45,7 @@ endif()
4345

4446
# Options
4547
option(CDDP_CPP_BUILD_TESTS "Whether to build tests." ON)
48+
option(CDDP_CPP_BUILD_EXAMPLES "Whether to build examples." ON)
4649

4750
# CasADi Configuration
4851
option(CDDP_CPP_CASADI "Whether to use CasADi" OFF)
@@ -92,16 +95,6 @@ endif()
9295
# Enable FetchContent for downloading dependencies
9396
include(FetchContent)
9497

95-
# Matplotplusplus
96-
FetchContent_Declare(matplotplusplus
97-
GIT_REPOSITORY https://github.com/alandefreitas/matplotplusplus
98-
GIT_TAG origin/master) # or whatever tag you want
99-
FetchContent_GetProperties(matplotplusplus)
100-
if(NOT matplotplusplus_POPULATED)
101-
FetchContent_Populate(matplotplusplus)
102-
add_subdirectory(${matplotplusplus_SOURCE_DIR} ${matplotplusplus_BINARY_DIR} EXCLUDE_FROM_ALL)
103-
endif()
104-
10598
# autodiff
10699
set(AUTODIFF_BUILD_TESTS OFF CACHE BOOL "Don't build autodiff tests")
107100
set(AUTODIFF_BUILD_EXAMPLES OFF CACHE BOOL "Don't build autodiff examples")
@@ -116,6 +109,7 @@ FetchContent_MakeAvailable(autodiff)
116109
# Googletest
117110
if (CDDP_CPP_BUILD_TESTS)
118111
enable_testing()
112+
set(INSTALL_GTEST OFF CACHE BOOL "Don't install GTest alongside cddp" FORCE)
119113
FetchContent_Declare(
120114
googletest
121115
GIT_REPOSITORY https://github.com/google/googletest.git
@@ -172,29 +166,33 @@ set(dynamics_model_srcs
172166
src/dynamics_model/euler_attitude.cpp
173167
)
174168

175-
add_library(${PROJECT_NAME}
169+
add_library(${PROJECT_NAME}
176170
${cddp_core_srcs}
177171
${dynamics_model_srcs}
178172
)
179173

180-
target_link_libraries(${PROJECT_NAME}
181-
$<IF:$<BOOL:${Eigen3_FOUND}>,Eigen3::Eigen,>
182-
matplot
183-
autodiff
184-
)
174+
# Enable position-independent code (required for Python shared library linking)
175+
set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
176+
177+
target_link_libraries(${PROJECT_NAME}
178+
PUBLIC
179+
$<IF:$<BOOL:${Eigen3_FOUND}>,Eigen3::Eigen,>
180+
autodiff
181+
)
185182

186183
if(NOT Eigen3_FOUND)
187184
target_include_directories(${PROJECT_NAME} PUBLIC ${EIGEN3_INCLUDE_DIRS})
188185
endif()
189186

190187
target_include_directories(${PROJECT_NAME} PUBLIC
191188
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/cddp-cpp>
192-
$<INSTALL_INTERFACE:include>
189+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
190+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/cddp-cpp>
193191
)
194192

195193
if (CDDP_CPP_CASADI)
196194
target_include_directories(${PROJECT_NAME} PUBLIC ${CASADI_INCLUDE_DIR})
197-
target_link_libraries(${PROJECT_NAME} ${CASADI_LIBRARIES})
195+
target_link_libraries(${PROJECT_NAME} PRIVATE ${CASADI_LIBRARIES})
198196
endif()
199197

200198
# ACADOS
@@ -241,7 +239,7 @@ if (CDDP_CPP_ACADOS)
241239
include_directories(${BLASFEO_INCLUDE_DIR})
242240
include_directories(${HPIPM_INCLUDE_DIR})
243241
link_directories(${ACADOS_LIB_DIR})
244-
target_link_libraries(${PROJECT_NAME} ${ACADOS_LIBRARIES})
242+
target_link_libraries(${PROJECT_NAME} PRIVATE ${ACADOS_LIBRARIES})
245243

246244
# Add preprocessor definition to enable ACADOS in code
247245
target_compile_definitions(${PROJECT_NAME} PRIVATE CDDP_CPP_ACADOS_ENABLED=1)
@@ -258,11 +256,52 @@ if (CDDP_CPP_BUILD_TESTS)
258256
endif()
259257

260258
# Build examples
261-
add_subdirectory(examples)
262-
263-
# Cmake compile commmand:
264-
# $ mkdir build
265-
# $ cd build
266-
# $ cmake -DCDDP_CPP_BUILD_TESTS=ON -DCDDP_CPP_CASADI=ON ..
267-
# $ make -j4
268-
# $ make test
259+
if (CDDP_CPP_BUILD_EXAMPLES)
260+
add_subdirectory(examples)
261+
endif()
262+
263+
# Python bindings (optional)
264+
option(CDDP_CPP_BUILD_PYTHON "Build Python bindings" OFF)
265+
if(CDDP_CPP_BUILD_PYTHON)
266+
add_subdirectory(python)
267+
endif()
268+
269+
# Install targets
270+
include(CMakePackageConfigHelpers)
271+
272+
install(TARGETS ${PROJECT_NAME}
273+
EXPORT cddpTargets
274+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
275+
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
276+
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
277+
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
278+
)
279+
280+
install(DIRECTORY include/cddp-cpp/
281+
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cddp-cpp
282+
FILES_MATCHING PATTERN "*.hpp"
283+
)
284+
285+
install(EXPORT cddpTargets
286+
FILE cddpTargets.cmake
287+
NAMESPACE cddp::
288+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cddp
289+
)
290+
291+
configure_package_config_file(
292+
${CMAKE_CURRENT_SOURCE_DIR}/cmake/cddpConfig.cmake.in
293+
${CMAKE_CURRENT_BINARY_DIR}/cddpConfig.cmake
294+
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cddp
295+
)
296+
297+
write_basic_package_version_file(
298+
${CMAKE_CURRENT_BINARY_DIR}/cddpConfigVersion.cmake
299+
VERSION ${PROJECT_VERSION}
300+
COMPATIBILITY SameMajorVersion
301+
)
302+
303+
install(FILES
304+
${CMAKE_CURRENT_BINARY_DIR}/cddpConfig.cmake
305+
${CMAKE_CURRENT_BINARY_DIR}/cddpConfigVersion.cmake
306+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cddp
307+
)

Dockerfile

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
99
curl \
1010
git \
1111
wget \
12-
libjpeg-dev \
13-
libpng-dev \
14-
gnuplot \
1512
libeigen3-dev && \
1613
rm -rf /var/lib/apt/lists/*
1714

@@ -31,15 +28,12 @@ WORKDIR /app
3128
COPY . /app
3229

3330

34-
# # Configure and build your project
35-
RUN rm -rf build && \
36-
mkdir build && \
37-
cd build && \
38-
cmake \
39-
-DCMAKE_BUILD_TYPE=Release \
40-
-DCDDP_CPP_BUILD_TESTS=ON \
41-
-DCDDP_CPP_CASADI=OFF \
42-
-DPython_EXECUTABLE=/usr/bin/python3 \
43-
.. && \
44-
make -j$(nproc) && \
45-
make test
31+
# Configure and build the project
32+
RUN rm -rf build && \
33+
cmake -S . -B build \
34+
-DCMAKE_BUILD_TYPE=Release \
35+
-DCDDP_CPP_BUILD_TESTS=ON \
36+
-DCDDP_CPP_BUILD_EXAMPLES=ON \
37+
-DCDDP_CPP_CASADI=OFF && \
38+
cmake --build build -j$(nproc) && \
39+
ctest --test-dir build --output-on-failure

0 commit comments

Comments
 (0)