diff --git a/README.md b/README.md index fd68f1c2..5f5d4a68 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,18 @@ To compile and install the GTDynamics python library: make && make python-install ``` + To generate stubs explicitly (useful for IDEs/type checkers), run: + + ```sh + make python-stubs + ``` + + On non-Windows platforms, `python-install` depends on `python-stubs`. + + For VS Code / Pylance setup (including `python.analysis.extraPaths`), see `python/README.md`. + + 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. + 4. To run the Python tests, you can simply run: ```sh diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 7a9b8d77..44345bb8 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -46,18 +46,47 @@ set_target_properties( RELWITHDEBINFO_POSTFIX "" # Otherwise you will have a wrong name ) -add_custom_target( - python-install - COMMAND ${PYTHON_EXECUTABLE} -m pip install . - DEPENDS ${PROJECT_NAME}_py - WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR}) - if(UNIX) set(GTD_PATH_SEP ":") else() set(GTD_PATH_SEP ";") endif() +set(GTD_PYTHON_INSTALL_EXTRA "") +set(GTD_PYTHON_STUB_MODULE "${PROJECT_NAME}.${PROJECT_NAME}") + +# Determine library path for stub generation +# On Linux/Unix, we need LD_LIBRARY_PATH; on macOS, DYLD_LIBRARY_PATH +get_target_property(GTSAM_LIBRARY_LOCATION gtsam LOCATION) +get_filename_component(GTSAM_LIBRARY_DIR "${GTSAM_LIBRARY_LOCATION}" DIRECTORY) + +if(APPLE) + set(GTD_STUB_LIB_PATH_VAR "DYLD_LIBRARY_PATH") +else() + set(GTD_STUB_LIB_PATH_VAR "LD_LIBRARY_PATH") +endif() + +add_custom_target( + python-stubs + COMMAND + ${CMAKE_COMMAND} -E env + "PYTHONPATH=${GTD_PYTHON_BINARY_DIR}${GTD_PATH_SEP}$ENV{PYTHONPATH}" + "${GTD_STUB_LIB_PATH_VAR}=${GTSAM_LIBRARY_DIR}${GTD_PATH_SEP}${CMAKE_LIBRARY_OUTPUT_DIRECTORY}${GTD_PATH_SEP}$ENV{${GTD_STUB_LIB_PATH_VAR}}" + ${PYTHON_EXECUTABLE} -m pybind11_stubgen -o . --ignore-all-errors + ${GTD_PYTHON_STUB_MODULE} + DEPENDS ${PROJECT_NAME}_py + WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR}) + +if(NOT WIN32) + list(APPEND GTD_PYTHON_INSTALL_EXTRA python-stubs) +endif() + +add_custom_target( + python-install + COMMAND ${PYTHON_EXECUTABLE} -m pip install . + DEPENDS ${PROJECT_NAME}_py ${GTD_PYTHON_INSTALL_EXTRA} + WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR}) + # Unit tests set(python_unit_test_suites) macro(PYTHON_UNIT_TEST_SUITE suiteName directory) diff --git a/python/README.md b/python/README.md index 45cfe4ed..e271e3fc 100644 --- a/python/README.md +++ b/python/README.md @@ -11,7 +11,7 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher ## Build prerequisites 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`. -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. +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. 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. ## Building and installing locally @@ -30,7 +30,47 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher ```sh make python-install ``` - This runs `${PYTHON_EXECUTABLE} -m pip install .` in `build/python`, which produces a wheel in `pip`'s cache before installing it. + 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. + +## GTSAM Python compatibility (important) + +Use a `gtsam` Python package built from the same GTSAM install/prefix that GTDynamics links against. +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). + +If you built GTSAM from source in a sibling repo, prepend it before importing: + +```sh +export PYTHONPATH=/path/to/gtsam/build/python:/path/to/GTDynamics/build/python:$PYTHONPATH +``` + +## Generating type stubs + +- Run `make python-stubs` to generate stubs with `pybind11-stubgen`. +- Stubs are generated in `build/python/gtdynamics/*.pyi`. +- The generated `gtdynamics.pyi` file is what tools like Pylance use for attribute completion/type checking on wrapped symbols. + +### VS Code / Pylance configuration + +If your runtime works but Pylance still reports unknown attributes (for example, `CreateRobotFromFile`), make sure VS Code analyzes the build package path. + +In workspace `.vscode/settings.json`: + +```json +{ + "python.defaultInterpreterPath": "/path/to/your/python", + "python.analysis.extraPaths": [ + "${workspaceFolder}/build/python" + ] +} +``` + +Then run: + +```sh +make python-stubs +``` + +and reload the VS Code window. ## Running Python tests @@ -40,9 +80,9 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher ## Packaging tips -- `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. -- 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. -- 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`. +- `python/templates/pyproject.toml.in` drives packaging in `build/python`. +- `make python-install` runs `pip install .` from `build/python`, which installs the generated extension module and package files from `build/python/gtdynamics`. +- If you need to publish a wheel manually, the wheel produced by `pip` already encodes the GTDynamics version reported by CMake. ## Wheels ### CI wheel pipeline