From 780a8ec881605e163350366cbf37817b539c1a58 Mon Sep 17 00:00:00 2001 From: SomeshG1151 <12someshgupta@email.com> Date: Sun, 2 Aug 2026 17:17:56 +0000 Subject: [PATCH 1/2] Add nightly Python API docs workflow for Gazebo Python bindings Adds a new CI workflow that builds Sphinx autodoc documentation for Gazebo libraries with Python bindings, following the same pattern as the existing nightly-upload.yml for Doxygen docs. - nightly-python-docs.yml: builds and deploys Python API docs - tools/build_python_docs.py: generates Sphinx HTML per distro - tools/restructure_python_artifacts.py: restructures artifacts for deployment to api/python/{lib}/{version}/ Prototype targets gz-math across harmonic, ionic, and jetty. Closes #663 --- .github/workflows/nightly-python-docs.yml | 83 ++++++++++++++++++ tools/build_python_docs.py | 101 ++++++++++++++++++++++ tools/restructure_python_artifacts.py | 55 ++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 .github/workflows/nightly-python-docs.yml create mode 100644 tools/build_python_docs.py create mode 100644 tools/restructure_python_artifacts.py diff --git a/.github/workflows/nightly-python-docs.yml b/.github/workflows/nightly-python-docs.yml new file mode 100644 index 0000000000..6e7ac96ab5 --- /dev/null +++ b/.github/workflows/nightly-python-docs.yml @@ -0,0 +1,83 @@ +name: Deploy Python API Docs +on: + schedule: + - cron: '0 7 * * *' + workflow_dispatch: +jobs: + build: + name: 'Build Python API Docs (${{ matrix.gazebo_distribution }})' + runs-on: ubuntu-latest + container: + image: ubuntu:${{ matrix.ubuntu_distribution }} + strategy: + fail-fast: false + matrix: + include: + - ubuntu_distribution: jammy + gazebo_distribution: harmonic + - ubuntu_distribution: noble + gazebo_distribution: ionic + - ubuntu_distribution: noble + gazebo_distribution: jetty + steps: + - uses: ros-tooling/setup-ros@v0.7 + - name: Set up Gazebo + uses: gazebo-tooling/setup-gazebo@v0.3.0 + with: + required-gazebo-distributions: ${{ matrix.gazebo_distribution }} + - name: Install Python bindings and Sphinx + run: | + sudo apt-get install -y python3-pip + pip3 install sphinx furo sphinx-autodoc-typehints + sudo apt-get install -y \ + python3-gz-math${{ matrix.gazebo_distribution == 'harmonic' && '7' || '' }} \ + || true + - name: Checkout docs repo + uses: actions/checkout@v4 + - name: Build Python API docs + run: | + python3 tools/build_python_docs.py \ + --distro ${{ matrix.gazebo_distribution }} \ + --output python-api-build + - uses: actions/upload-artifact@v4 + if: always() + with: + name: python-api-docs-${{ matrix.gazebo_distribution }} + path: python-api-build + if-no-files-found: warn + include-hidden-files: 'true' + upload: + name: Upload Python docs to production + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + concurrency: + group: pages + cancel-in-progress: false + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + id: download + with: + path: .python-api-docs + pattern: python-api-docs-* + merge-multiple: false + - name: Install Python + run: | + sudo apt-get install -y python3-yaml + - run: python3 tools/restructure_python_artifacts.py --input ${{steps.download.outputs.download-path}} .python-api-out + - uses: actions/upload-artifact@v4 + with: + name: python-api-docs + path: .python-api-out/* + if-no-files-found: warn + include-hidden-files: 'true' + - name: Commit + if: github.ref == 'refs/heads/master' + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: ./.python-api-out + target-folder: api/python + clean: false diff --git a/tools/build_python_docs.py b/tools/build_python_docs.py new file mode 100644 index 0000000000..e66046e37c --- /dev/null +++ b/tools/build_python_docs.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +""" +Build Sphinx autodoc documentation for Gazebo Python bindings. +Usage: python3 build_python_docs.py --distro harmonic --output python-api-build +""" + +import argparse +import subprocess +import sys +import shutil +from pathlib import Path + +# Map distro -> list of (module_name, package_name, lib_short_name) +PYTHON_BINDINGS = { + "harmonic": [ + ("gz.math7", "gz-math7", "math7"), + ], + "ionic": [ + ("gz.math8", "gz-math8", "math8"), + ], + "jetty": [ + ("gz.math", "gz-math", "math"), + ], +} + +CONF_PY_TEMPLATE = """\ +project = "{project}" +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", +] +html_theme = "furo" +autodoc_default_options = {{ + "members": True, + "undoc-members": True, + "show-inheritance": True, +}} +""" + +INDEX_RST_TEMPLATE = """\ +{project} +{underline} + +.. automodule:: {module} + :members: + :undoc-members: + :show-inheritance: +""" + + +def build_docs(module_name, lib_short_name, output_dir: Path): + build_dir = output_dir / lib_short_name + src_dir = build_dir / "src" + src_dir.mkdir(parents=True, exist_ok=True) + + project = f"Gazebo {lib_short_name} Python API" + + (src_dir / "conf.py").write_text(CONF_PY_TEMPLATE.format(project=project)) + (src_dir / "index.rst").write_text( + INDEX_RST_TEMPLATE.format( + project=project, + underline="=" * len(project), + module=module_name, + ) + ) + + html_out = build_dir / "html" + result = subprocess.run( + ["sphinx-build", "-b", "html", str(src_dir), str(html_out)], + capture_output=True, text=True + ) + print(result.stdout) + if result.returncode != 0: + print("WARN: sphinx-build failed for", module_name, file=sys.stderr) + print(result.stderr, file=sys.stderr) + return False + return True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--distro", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + + output_dir = Path(args.output) + output_dir.mkdir(parents=True, exist_ok=True) + + bindings = PYTHON_BINDINGS.get(args.distro, []) + if not bindings: + print(f"No Python bindings configured for distro: {args.distro}") + sys.exit(0) + + for module_name, pkg_name, lib_short_name in bindings: + print(f"Building docs for {module_name}...") + build_docs(module_name, lib_short_name, output_dir) + + +if __name__ == "__main__": + main() diff --git a/tools/restructure_python_artifacts.py b/tools/restructure_python_artifacts.py new file mode 100644 index 0000000000..d37b989ccf --- /dev/null +++ b/tools/restructure_python_artifacts.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +""" +Restructure Python API doc artifacts for deployment. + +Input structure: + python-api-docs-harmonic/math7/html/ + python-api-docs-ionic/math8/html/ + +Output structure: + math/7/ + math/8/ +""" + +import argparse +import re +import sys +import shutil +from pathlib import Path + + +def restructure(input_dir: Path, output_dir: Path): + output_dir.mkdir(parents=True, exist_ok=True) + for distro_dir in input_dir.iterdir(): + if not distro_dir.is_dir(): + continue + for lib_dir in distro_dir.iterdir(): + if not lib_dir.is_dir(): + continue + html_dir = lib_dir / "html" + if not html_dir.exists(): + print(f"WARN: no html dir in {lib_dir}, skipping") + continue + # lib_dir.name is like "math7" or "math" + m = re.match(r"([a-z_]+)(\d*)", lib_dir.name) + if not m: + continue + lib_name, version = m.groups() + if not version: + version = "latest" + dest = output_dir / lib_name / version + dest.mkdir(parents=True, exist_ok=True) + print(f"{html_dir} -> {dest}") + shutil.copytree(html_dir, dest, dirs_exist_ok=True) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--input", dest="input_dir", required=True) + parser.add_argument("output_dir") + args = parser.parse_args() + restructure(Path(args.input_dir), Path(args.output_dir)) + + +if __name__ == "__main__": + sys.exit(main()) From e1aea0b2a61f9091a07dbca746a9c26cc467a628 Mon Sep 17 00:00:00 2001 From: SomeshG1151 <12someshgupta@email.com> Date: Sun, 2 Aug 2026 17:23:21 +0000 Subject: [PATCH 2/2] Fix matrix expression syntax in package install step --- .github/workflows/nightly-python-docs.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nightly-python-docs.yml b/.github/workflows/nightly-python-docs.yml index 6e7ac96ab5..e61b1eb5ae 100644 --- a/.github/workflows/nightly-python-docs.yml +++ b/.github/workflows/nightly-python-docs.yml @@ -15,10 +15,13 @@ jobs: include: - ubuntu_distribution: jammy gazebo_distribution: harmonic + gz_math_version: "7" - ubuntu_distribution: noble gazebo_distribution: ionic + gz_math_version: "8" - ubuntu_distribution: noble gazebo_distribution: jetty + gz_math_version: "" steps: - uses: ros-tooling/setup-ros@v0.7 - name: Set up Gazebo @@ -29,9 +32,7 @@ jobs: run: | sudo apt-get install -y python3-pip pip3 install sphinx furo sphinx-autodoc-typehints - sudo apt-get install -y \ - python3-gz-math${{ matrix.gazebo_distribution == 'harmonic' && '7' || '' }} \ - || true + sudo apt-get install -y python3-gz-math${{ matrix.gz_math_version }} || true - name: Checkout docs repo uses: actions/checkout@v4 - name: Build Python API docs