diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 429e11c..3d9d6b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,3 +34,16 @@ jobs: - name: Run tests run: pixi run test + + - name: Install Windows ROS activation test environment + if: runner.os == 'Windows' + run: >- + pixi exec --spec conda -- conda env create --yes + --prefix "${{ runner.temp }}/vinca-ros-activation" + --file tests/windows/environment.yml + + - name: Test ROS activation with conda and Git Bash + if: runner.os == 'Windows' + env: + VINCA_WINDOWS_ROS_PREFIX: ${{ runner.temp }}/vinca-ros-activation + run: pixi run pytest -v vinca/test_windows_activation.py diff --git a/tests/windows/README.md b/tests/windows/README.md new file mode 100644 index 0000000..80b2dc4 --- /dev/null +++ b/tests/windows/README.md @@ -0,0 +1,24 @@ +The Windows CI job installs `environment.yml` into a disposable conda environment +and runs `vinca/test_windows_activation.py` using Git for Windows Bash. The test +renders and installs the PR's hooks, then activates the environment through real +conda. It checks the conda-build guard, ROS variables, native Python's ament index +lookup, repeated activation, and deactivation. It restores the installed hooks +afterward. An additional package with real `.dsv` hooks exercises PATH prepends, +appends and assignments, PYTHONPATH prepends, a CMAKE_PREFIX_PATH append, and paths +containing spaces. The assignment case replaces the inherited PATH with an +explicit Windows path list and checks its exact contents and order. + +To reproduce in PowerShell from the repository root: + +```powershell +pixi exec --spec conda -- conda env create --yes --prefix "$PWD/.pixi/ros-activation" --file tests/windows/environment.yml +$env:VINCA_WINDOWS_ROS_PREFIX = "$PWD/.pixi/ros-activation" +pixi run pytest -v vinca/test_windows_activation.py +``` + +The integration test skips unless `VINCA_WINDOWS_ROS_PREFIX` is set, and skips on +non-Windows platforms. Once a prefix is supplied, missing dependencies or Git Bash +fail the test rather than skipping it. Use a disposable environment: the test +temporarily replaces its ros_workspace activation hooks. + +This test does not run a full rclcpp CMake build. diff --git a/tests/windows/environment.yml b/tests/windows/environment.yml new file mode 100644 index 0000000..4d1ca09 --- /dev/null +++ b/tests/windows/environment.yml @@ -0,0 +1,11 @@ +name: vinca-ros-activation +channels: + - robostack-jazzy + - conda-forge + - nodefaults +dependencies: + - python=3.12 + - conda + - ros-jazzy-ros-workspace + - ros-jazzy-ros-environment + - ros-jazzy-ament-index-python diff --git a/vinca/templates/activate.sh.in b/vinca/templates/activate.sh.in index 4a35f58..db2b1b4 100644 --- a/vinca/templates/activate.sh.in +++ b/vinca/templates/activate.sh.in @@ -4,23 +4,92 @@ # exit 0; # fi -# Not sure if this is necessary on UNIX? -# export QT_PLUGIN_PATH=$CONDA_PREFIX\plugins +case "$OSTYPE" in + msys*|cygwin*) + # Windows: conda runs this script (not activate.bat) when the environment + # is activated from Git Bash / MSYS2 / Cygwin. The ROS packages only ship + # .bat/.dsv environment hooks there (under %CONDA_PREFIX%\Library), so do + # what activate.bat -> local_setup.bat does: let ament's + # _local_setup_util.py evaluate every package's .dsv hooks, in "sh" mode, + # and apply the result. Values stay in Windows form (C:\...;C:\...) since + # native Windows programs (python, cmake, ros2) read them; only PATH is + # converted to the form the shell itself uses. + if [ -n "${SYS_PREFIX:-}" ]; then + # Don't do anything when we are in conda build (as activate.bat). + : + elif ! command -v cygpath > /dev/null 2>&1; then + echo "Not activating ROS: cygpath not found" >&2 + else + # Keep an absolute path: a .dsv assignment may remove cygpath from PATH. + _ros_cygpath="$(command -v cygpath)" + _ros_prefix_w="$(cygpath -w "$CONDA_PREFIX")" + _ros_python="$(cygpath -u "$_ros_prefix_w\\python.exe")" + _ros_util="$(cygpath -u "$_ros_prefix_w\\Library\\_local_setup_util.py")" -if [ "$CONDA_BUILD" = "1" -a "$target_platform" != "$build_platform" ]; then - # ignore sourcing - echo "Not activating ROS when cross-compiling"; -else - source $CONDA_PREFIX/setup.sh -fi + export QT_PLUGIN_PATH="$_ros_prefix_w\\Library\\plugins" + unset PYTHONHOME -case "$OSTYPE" in - darwin*) export ROS_OS_OVERRIDE="conda:osx";; - linux*) export ROS_OS_OVERRIDE="conda:linux";; -esac + if [ -f "$_ros_util" ]; then + while IFS= read -r _ros_line; do + case "$_ros_line" in + "export "*"#:}") + # strip a leading separator: ';' on Windows, not ':' + _ros_line="${_ros_line%#:\}}#;}" + ;; + "export "*"%:}") + # strip a trailing separator: ';' on Windows, not ':' + _ros_line="${_ros_line%\%:\}}%;}" + ;; + esac + case "$_ros_line" in + "export PATH="*) + # ament emits Windows lists even in sh mode. Evaluate against a + # Windows PATH in a subshell, then convert the complete result. + # This handles prepends, appends and assignments without mixing + # semicolons, drive-letter colons and Bash's ':' separators. + _ros_value="$( + PATH="$("$_ros_cygpath" -w -p "$PATH")" + eval "$_ros_line" + printf '%s' "$PATH" + )" + export PATH="$("$_ros_cygpath" -u -p "$_ros_value")" + ;; + "export "*) + eval "$_ros_line" + ;; + esac + done <