From c4a7be45cb3b4d177c43896edb882a88b58b90d2 Mon Sep 17 00:00:00 2001 From: Tomo Sasaki Date: Thu, 2 Apr 2026 21:50:58 -0400 Subject: [PATCH 1/3] Add PyPI release and GitHub Pages automation Set up PyPI release metadata and CI workflows for wheels and trusted publishing. Add an MkDocs site and Pages deployment workflow so package and docs publishing are automated together. --- .github/workflows/pages.yml | 58 ++++++++++++++++++++++ .github/workflows/publish.yml | 91 +++++++++++++++++++++++++++++++++++ .github/workflows/wheels.yml | 70 +++++++++++++++++++++++++++ .gitignore | 1 + CMakeLists.txt | 4 +- CONTRIBUTING.md | 9 ++++ README.md | 20 +++++++- docs/cpp.md | 32 ++++++++++++ docs/index.md | 34 +++++++++++++ docs/install.md | 57 ++++++++++++++++++++++ docs/python.md | 39 +++++++++++++++ docs/release.md | 48 ++++++++++++++++++ mkdocs.yml | 29 +++++++++++ pyproject.toml | 30 +++++++++++- 14 files changed, 518 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/pages.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/wheels.yml create mode 100644 docs/cpp.md create mode 100644 docs/index.md create mode 100644 docs/install.md create mode 100644 docs/python.md create mode 100644 docs/release.md create mode 100644 mkdocs.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..6967b19 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,58 @@ +name: Docs + +on: + push: + branches: [master] + paths: + - "docs/**" + - "mkdocs.yml" + - ".github/workflows/pages.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + name: Build docs + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install uv + run: python -m pip install uv + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Build site + run: uvx --from mkdocs --with mkdocs-material mkdocs build --strict + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + name: Deploy docs + runs-on: ubuntu-latest + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..8859b0f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,91 @@ +name: Publish PyPI + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +jobs: + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build frontend + run: python -m pip install build + + - name: Build source distribution + run: python -m build --sdist + + - name: Upload source distribution + uses: actions/upload-artifact@v4 + with: + name: dist-sdist + path: dist/*.tar.gz + + build-wheels: + name: Build wheels (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install cibuildwheel + run: python -m pip install cibuildwheel + + - name: Build wheels + run: python -m cibuildwheel --output-dir wheelhouse + env: + CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* + CIBW_SKIP: pp* *-musllinux_* *-win32 + CIBW_ARCHS_LINUX: auto64 + CIBW_ARCHS_WINDOWS: auto64 + CIBW_ARCHS_MACOS: auto64 + + - name: Upload wheel artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-wheels-${{ matrix.os }} + path: wheelhouse/*.whl + + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: [build-sdist, build-wheels] + permissions: + id-token: write + contents: read + environment: + name: pypi + url: https://pypi.org/p/pycddp + + steps: + - name: Download distribution artifacts + uses: actions/download-artifact@v4 + with: + pattern: dist-* + path: dist + merge-multiple: true + + - name: Publish distributions + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..4d4c161 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,70 @@ +name: Wheel CI + +on: + pull_request: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build frontend + run: python -m pip install build + + - name: Build source distribution + run: python -m build --sdist + + - name: Upload source distribution + uses: actions/upload-artifact@v4 + with: + name: dist-sdist + path: dist/*.tar.gz + + build-wheels: + name: Build wheels (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install cibuildwheel + run: python -m pip install cibuildwheel + + - name: Build wheels + run: python -m cibuildwheel --output-dir wheelhouse + env: + CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* + CIBW_SKIP: pp* *-musllinux_* *-win32 + CIBW_ARCHS_LINUX: auto64 + CIBW_ARCHS_WINDOWS: auto64 + CIBW_ARCHS_MACOS: auto64 + + - name: Upload wheel artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-wheels-${{ matrix.os }} + path: wheelhouse/*.whl diff --git a/.gitignore b/.gitignore index 243ac7e..f58f9ed 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ solver_snopt.out # Python / uv .venv/ uv.lock +site/ *.egg-info/ __pycache__/ *.so diff --git a/CMakeLists.txt b/CMakeLists.txt index 943047c..ebf709d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.15) # Set policy to support older CMake code cmake_policy(SET CMP0048 NEW) # Allow policy version minimum flag to be passed to sub-projects -set(CMAKE_POLICY_VERSION_MINIMUM "3.5" CACHE STRING "Minimum CMake version for policy compatibility") +set(CMAKE_POLICY_VERSION_MINIMUM "3.15" CACHE STRING "Minimum CMake version for policy compatibility") project( cddp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8099370..6abcc7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,12 @@ source .venv/bin/activate pytest -q python/tests ``` +### Docs preview + +```bash +uvx --from mkdocs --with mkdocs-material mkdocs serve +``` + ## Pull requests Before opening a pull request: @@ -50,6 +56,9 @@ Before opening a pull request: 4. Update documentation when user-facing behavior, examples, or public APIs change. 5. Write a clear commit message and PR description. +Tagged releases publish Python artifacts to PyPI via GitHub Actions. The +project site deploys from `master` to GitHub Pages. + PRs that include a minimal reproduction, exact validation commands, and a concise explanation of the design tradeoffs are much easier to review. ## Issues diff --git a/README.md b/README.md index 5cb6d3b..00c3231 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,18 @@ sudo apt-get install libeigen3-dev # For Ubuntu brew install eigen # For macOS ``` -### Building +### Python package +Tagged releases publish the Python bindings to PyPI: + +```bash +pip install pycddp +``` + +Prebuilt wheels are intended for CPython 3.10-3.13 on Linux, macOS, and +Windows. If a wheel is not available for your platform yet, install from +source using the steps below. + +### Building from source ```bash git clone https://github.com/astomodynamics/cddp-cpp cd cddp-cpp @@ -105,6 +116,13 @@ make -j4 make test ``` +### Documentation +The project site is published through GitHub Pages at: + + + +The Pages workflow builds the Markdown docs from `docs/` using MkDocs. + ## ROS If you want to use this library for ROS2 MPC node, please refer [CDDP MPC Package](https://github.com/astomodynamics/cddp_mpc). You do not need to install this library to use the package. MPC package will automatically install this library as a dependency. diff --git a/docs/cpp.md b/docs/cpp.md new file mode 100644 index 0000000..4b5a46b --- /dev/null +++ b/docs/cpp.md @@ -0,0 +1,32 @@ +# C++ build + +The repository remains a first-class C++ library even when distributed through +PyPI for Python users. + +## Core build + +```bash +cmake -S . -B build +cmake --build build -j4 +ctest --test-dir build --output-on-failure +``` + +## Python bindings from CMake + +```bash +cmake -S . -B build-python -DCDDP_CPP_BUILD_PYTHON=ON -DCDDP_CPP_BUILD_TESTS=OFF -DCDDP_CPP_BUILD_EXAMPLES=OFF +cmake --build build-python -j4 +``` + +## Installed C++ assets + +The install rules export: + +- `libcddp` and CMake package metadata +- public headers under `include/cddp-cpp/` +- the Python extension when `CDDP_CPP_BUILD_PYTHON=ON` + +That lets the repository serve both as: + +- a source build for C++ consumers +- the native backend for the `pycddp` wheel diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..34fa970 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,34 @@ +# CDDP + +`cddp-cpp` is a constrained differential dynamic programming solver library with: + +- a C++17 core library for trajectory optimization and MPC +- `pycddp` Python bindings built with `pybind11` +- a small animation-oriented Python portfolio for demos and regression checks + +Use the navigation to get started with installation, local development, and +the release workflow for PyPI and GitHub Pages. + +## Project scope + +- C++ library: reusable solver and dynamical-system implementations +- Python package: importable bindings distributed as `pycddp` +- Docs site: Markdown content from `docs/`, published to GitHub Pages + +## Quick start + +Install the Python package from PyPI: + +```bash +pip install pycddp +``` + +Or build from source: + +```bash +git clone https://github.com/astomodynamics/cddp-cpp +cd cddp-cpp +cmake -S . -B build +cmake --build build -j4 +ctest --test-dir build --output-on-failure +``` diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..5a1d3c0 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,57 @@ +# Installation + +## Python users + +Install the published wheel from PyPI: + +```bash +pip install pycddp +``` + +Target release support is: + +- CPython 3.10-3.13 +- Linux x86_64 +- macOS x86_64 and arm64 +- Windows x86_64 + +If your platform does not have a wheel yet, `pip` will fall back to the source +distribution and build locally. + +## Source build prerequisites + +- CMake 3.15+ +- a C++17 compiler +- Eigen 3 +- Python 3.10+ when building the bindings + +Ubuntu example: + +```bash +sudo apt-get install build-essential cmake libeigen3-dev +``` + +macOS example: + +```bash +brew install cmake eigen +``` + +## Local developer install + +For repository work: + +```bash +uv venv .venv --python 3.12 +uv sync +source .venv/bin/activate +pytest -q python/tests +``` + +## Build the Python wheel locally + +```bash +uv build +``` + +This creates `dist/*.tar.gz` and `dist/*.whl`. diff --git a/docs/python.md b/docs/python.md new file mode 100644 index 0000000..bc9d000 --- /dev/null +++ b/docs/python.md @@ -0,0 +1,39 @@ +# Python package + +The Python package is published as `pycddp`. + +## What is included + +- the native extension module `_pycddp_core` +- the public package namespace `pycddp` +- version metadata from `pycddp._version` + +## Local validation + +Run the Python tests after syncing the environment: + +```bash +source .venv/bin/activate +pytest -q python/tests +``` + +Build an installable artifact: + +```bash +uv build +``` + +Smoke-test the wheel in a fresh environment: + +```bash +uv venv /tmp/pycddp-smoke --python 3.13 +source /tmp/pycddp-smoke/bin/activate +uv pip install dist/*.whl +python -c "import pycddp; print(pycddp.__version__)" +``` + +## Release strategy + +- pull requests and pushes validate wheel builds in GitHub Actions +- tagged releases build wheels plus an sdist and publish to PyPI +- publishing uses PyPI Trusted Publishing instead of a long-lived API token diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..d48cde0 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,48 @@ +# Release operations + +This repository now has three distinct automation paths: + +- `wheels.yml`: validates wheel and sdist builds on pull requests and pushes +- `publish.yml`: builds release artifacts from a Git tag and publishes to PyPI +- `pages.yml`: publishes the documentation site to GitHub Pages + +## One-time PyPI setup + +Before the first release, configure PyPI Trusted Publishing for project +`pycddp`. + +On PyPI: + +1. Create the project if it does not exist yet. +2. Add a trusted publisher for GitHub Actions. +3. Set owner to `astomodynamics`. +4. Set repository to `cddp-cpp`. +5. Set workflow to `.github/workflows/publish.yml`. +6. Set environment name to `pypi`. + +After that, pushing a tag like `v0.1.0` will publish the matching artifacts. + +## One-time GitHub Pages setup + +On GitHub: + +1. Open repository settings. +2. Open Pages. +3. Set the source to `GitHub Actions`. + +After that, pushes to `master` that touch the docs or `mkdocs.yml` will deploy +the site. + +## Release steps + +1. Update package version if needed. +2. Merge the release changes to `master`. +3. Create and push a tag: + +```bash +git tag v0.1.0 +git push origin v0.1.0 +``` + +4. Verify the `publish.yml` workflow succeeds. +5. Confirm the release appears on PyPI and the docs site is current. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..0db7274 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,29 @@ +site_name: CDDP +site_description: Constrained differential dynamic programming in C++ with Python bindings. +site_url: https://astomodynamics.github.io/cddp-cpp/ +repo_url: https://github.com/astomodynamics/cddp-cpp +repo_name: astomodynamics/cddp-cpp + +theme: + name: material + features: + - navigation.instant + - navigation.sections + - navigation.top + - content.code.copy + +plugins: + - search + +markdown_extensions: + - admonition + - pymdownx.highlight + - pymdownx.superfences + +nav: + - Overview: index.md + - Installation: install.md + - Python API: python.md + - C++ Build: cpp.md + - Portfolio: python_portfolio.md + - Release Ops: release.md diff --git a/pyproject.toml b/pyproject.toml index 57e2b2e..09cea5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,10 +7,34 @@ name = "pycddp" version = "0.1.0" description = "Python bindings for CDDP trajectory optimization" readme = "README.md" -license = {text = "Apache-2.0"} +authors = [{name = "Tomo Sasaki"}] +license = "Apache-2.0" +license-files = ["LICENSE"] requires-python = ">=3.10" +keywords = ["trajectory-optimization", "model-predictive-control", "differential-dynamic-programming", "optimal-control", "robotics"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Scientific/Engineering", +] dependencies = ["numpy>=1.22"] +[project.urls] +Homepage = "https://github.com/astomodynamics/cddp-cpp" +Repository = "https://github.com/astomodynamics/cddp-cpp" +Documentation = "https://astomodynamics.github.io/cddp-cpp/" +Issues = "https://github.com/astomodynamics/cddp-cpp/issues" + [project.optional-dependencies] test = ["pytest>=7.0", "matplotlib>=3.5"] viz = ["matplotlib>=3.5"] @@ -21,3 +45,7 @@ dev = ["pytest>=7.0", "matplotlib>=3.5"] [tool.scikit-build] cmake.args = ["-DCDDP_CPP_BUILD_PYTHON=ON", "-DCDDP_CPP_BUILD_TESTS=OFF", "-DCDDP_CPP_BUILD_EXAMPLES=OFF"] wheel.packages = ["python/pycddp"] + +[tool.cibuildwheel] +build-verbosity = 1 +test-command = "python -c \"import pycddp; print(pycddp.__version__)\"" From 7f9876fd263a3898a6ac8d17a53d9a6ad3a44ed3 Mon Sep 17 00:00:00 2001 From: Tomo Sasaki Date: Thu, 2 Apr 2026 21:59:22 -0400 Subject: [PATCH 2/3] Fix wheel builds when Eigen is fetched Include FetchContent before the Eigen fallback path so cibuildwheel can configure on runners without a system Eigen installation. Drop the no-op pp* skip selector to keep the wheel workflow output clean. --- .github/workflows/publish.yml | 2 +- .github/workflows/wheels.yml | 2 +- CMakeLists.txt | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8859b0f..a11956d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -57,7 +57,7 @@ jobs: run: python -m cibuildwheel --output-dir wheelhouse env: CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* - CIBW_SKIP: pp* *-musllinux_* *-win32 + CIBW_SKIP: *-musllinux_* *-win32 CIBW_ARCHS_LINUX: auto64 CIBW_ARCHS_WINDOWS: auto64 CIBW_ARCHS_MACOS: auto64 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 4d4c161..f7629ee 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -58,7 +58,7 @@ jobs: run: python -m cibuildwheel --output-dir wheelhouse env: CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* - CIBW_SKIP: pp* *-musllinux_* *-win32 + CIBW_SKIP: *-musllinux_* *-win32 CIBW_ARCHS_LINUX: auto64 CIBW_ARCHS_WINDOWS: auto64 CIBW_ARCHS_MACOS: auto64 diff --git a/CMakeLists.txt b/CMakeLists.txt index ebf709d..79b2547 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ project( ) include(GNUInstallDirs) +include(FetchContent) set(CMAKE_CXX_STANDARD 17) # Enforce C++17 as the minimum standard set(CMAKE_CXX_STANDARD_REQUIRED ON) # Enforce C++17 as the minimum standard @@ -92,9 +93,6 @@ if (CDDP_CPP_CASADI) endif() endif() -# Enable FetchContent for downloading dependencies -include(FetchContent) - # autodiff set(AUTODIFF_BUILD_TESTS OFF CACHE BOOL "Don't build autodiff tests") set(AUTODIFF_BUILD_EXAMPLES OFF CACHE BOOL "Don't build autodiff examples") From 4ec069a83947b5d87f4979c24c2b38749bbbbd30 Mon Sep 17 00:00:00 2001 From: Tomo Sasaki Date: Thu, 2 Apr 2026 22:19:32 -0400 Subject: [PATCH 3/3] Fix macOS PyPI classifier Use the valid MacOS X Trove classifier suggested in review while keeping the SPDX license form that passes the package build. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 09cea5a..38cf25b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS", + "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: C++", "Programming Language :: Python :: 3",