Skip to content

Commit 05bba67

Browse files
authored
Merge pull request #422 from borglab/copilot/sub-pr-421
Add Python stub generation infrastructure
2 parents b1a1b13 + d2ee246 commit 05bba67

3 files changed

Lines changed: 92 additions & 11 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,18 @@ To compile and install the GTDynamics python library:
240240
make && make python-install
241241
```
242242
243+
To generate stubs explicitly (useful for IDEs/type checkers), run:
244+
245+
```sh
246+
make python-stubs
247+
```
248+
249+
On non-Windows platforms, `python-install` depends on `python-stubs`.
250+
251+
For VS Code / Pylance setup (including `python.analysis.extraPaths`), see `python/README.md`.
252+
253+
Important: use a `gtsam` Python package built from the same install/prefix as the GTSAM library linked into GTDynamics. Mixing a local GTDynamics build with an unrelated pip/conda `gtsam` wheel can cause runtime aborts.
254+
243255
4. To run the Python tests, you can simply run:
244256
245257
```sh

python/CMakeLists.txt

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,47 @@ set_target_properties(
4646
RELWITHDEBINFO_POSTFIX "" # Otherwise you will have a wrong name
4747
)
4848

49-
add_custom_target(
50-
python-install
51-
COMMAND ${PYTHON_EXECUTABLE} -m pip install .
52-
DEPENDS ${PROJECT_NAME}_py
53-
WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR})
54-
5549
if(UNIX)
5650
set(GTD_PATH_SEP ":")
5751
else()
5852
set(GTD_PATH_SEP ";")
5953
endif()
6054

55+
set(GTD_PYTHON_INSTALL_EXTRA "")
56+
set(GTD_PYTHON_STUB_MODULE "${PROJECT_NAME}.${PROJECT_NAME}")
57+
58+
# Determine library path for stub generation
59+
# On Linux/Unix, we need LD_LIBRARY_PATH; on macOS, DYLD_LIBRARY_PATH
60+
get_target_property(GTSAM_LIBRARY_LOCATION gtsam LOCATION)
61+
get_filename_component(GTSAM_LIBRARY_DIR "${GTSAM_LIBRARY_LOCATION}" DIRECTORY)
62+
63+
if(APPLE)
64+
set(GTD_STUB_LIB_PATH_VAR "DYLD_LIBRARY_PATH")
65+
else()
66+
set(GTD_STUB_LIB_PATH_VAR "LD_LIBRARY_PATH")
67+
endif()
68+
69+
add_custom_target(
70+
python-stubs
71+
COMMAND
72+
${CMAKE_COMMAND} -E env
73+
"PYTHONPATH=${GTD_PYTHON_BINARY_DIR}${GTD_PATH_SEP}$ENV{PYTHONPATH}"
74+
"${GTD_STUB_LIB_PATH_VAR}=${GTSAM_LIBRARY_DIR}${GTD_PATH_SEP}${CMAKE_LIBRARY_OUTPUT_DIRECTORY}${GTD_PATH_SEP}$ENV{${GTD_STUB_LIB_PATH_VAR}}"
75+
${PYTHON_EXECUTABLE} -m pybind11_stubgen -o . --ignore-all-errors
76+
${GTD_PYTHON_STUB_MODULE}
77+
DEPENDS ${PROJECT_NAME}_py
78+
WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR})
79+
80+
if(NOT WIN32)
81+
list(APPEND GTD_PYTHON_INSTALL_EXTRA python-stubs)
82+
endif()
83+
84+
add_custom_target(
85+
python-install
86+
COMMAND ${PYTHON_EXECUTABLE} -m pip install .
87+
DEPENDS ${PROJECT_NAME}_py ${GTD_PYTHON_INSTALL_EXTRA}
88+
WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR})
89+
6190
# Unit tests
6291
set(python_unit_test_suites)
6392
macro(PYTHON_UNIT_TEST_SUITE suiteName directory)

python/README.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher
1111
## Build prerequisites
1212

1313
1. **GTSAM** must be built with Python support (i.e., `-DGTSAM_BUILD_PYTHON=ON`) and installed to a prefix that GTDynamics can see via `GTSAM_DIR` or `CMAKE_PREFIX_PATH`.
14-
2. **Python tooling**: the CI job installs `setuptools<70`, `wheel`, `numpy`, `pyparsing`, `pyyaml`, and `pybind11-stubgen` before configuring the project; matching this list locally avoids the same runtime issues.
14+
2. **Python tooling**: the CI job installs `setuptools<70`, `wheel`, `numpy`, `pyparsing`, `pyyaml`, and `pybind11-stubgen` before configuring the project; matching this list locally avoids the same runtime issues. `pybind11-stubgen` is required for the `python-stubs` CMake target.
1515
3. On macOS, the workflow creates and activates a virtual environment (`pythonX -m venv venv`) so that `pip install` and the tests run in the same interpreter that baked the bindings.
1616

1717
## Building and installing locally
@@ -30,7 +30,47 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher
3030
```sh
3131
make python-install
3232
```
33-
This runs `${PYTHON_EXECUTABLE} -m pip install .` in `build/python`, which produces a wheel in `pip`'s cache before installing it.
33+
This runs `${PYTHON_EXECUTABLE} -m pip install .` in `build/python`, which produces a wheel in `pip`'s cache before installing it. On non-Windows platforms, `python-install` depends on `python-stubs`, so `.pyi` files are generated first.
34+
35+
## GTSAM Python compatibility (important)
36+
37+
Use a `gtsam` Python package built from the same GTSAM install/prefix that GTDynamics links against.
38+
Mixing a local GTDynamics build with an unrelated pip/conda `gtsam` wheel can cause hard runtime failures (for example, process aborts when adding factors to a graph).
39+
40+
If you built GTSAM from source in a sibling repo, prepend it before importing:
41+
42+
```sh
43+
export PYTHONPATH=/path/to/gtsam/build/python:/path/to/GTDynamics/build/python:$PYTHONPATH
44+
```
45+
46+
## Generating type stubs
47+
48+
- Run `make python-stubs` to generate stubs with `pybind11-stubgen`.
49+
- Stubs are generated in `build/python/gtdynamics/*.pyi`.
50+
- The generated `gtdynamics.pyi` file is what tools like Pylance use for attribute completion/type checking on wrapped symbols.
51+
52+
### VS Code / Pylance configuration
53+
54+
If your runtime works but Pylance still reports unknown attributes (for example, `CreateRobotFromFile`), make sure VS Code analyzes the build package path.
55+
56+
In workspace `.vscode/settings.json`:
57+
58+
```json
59+
{
60+
"python.defaultInterpreterPath": "/path/to/your/python",
61+
"python.analysis.extraPaths": [
62+
"${workspaceFolder}/build/python"
63+
]
64+
}
65+
```
66+
67+
Then run:
68+
69+
```sh
70+
make python-stubs
71+
```
72+
73+
and reload the VS Code window.
3474

3575
## Running Python tests
3676

@@ -40,9 +80,9 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher
4080

4181
## Packaging tips
4282

43-
- `python/templates/setup.py.in` reads the CMake-generated `requirements.txt` and packages the shared library blobs (`.so` / `.pyd`) from `python/gtdynamics` so running `pip wheel .` in `build/python` yields a complete asset.
44-
- Keep `python/requirements.txt` in sync with the requirements file copied to `build/python/requirements.txt` so that CI and a local `pip install` use the same dependency list.
45-
- If you need to publish a wheel manually, the packaged wheel that `pip install .` writes to `~/.cache/pip` already encodes the GTDynamics version reported by `CMakeLists.txt`.
83+
- `python/templates/pyproject.toml.in` drives packaging in `build/python`.
84+
- `make python-install` runs `pip install .` from `build/python`, which installs the generated extension module and package files from `build/python/gtdynamics`.
85+
- If you need to publish a wheel manually, the wheel produced by `pip` already encodes the GTDynamics version reported by CMake.
4686

4787
## Wheels
4888
### CI wheel pipeline

0 commit comments

Comments
 (0)