# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: Python package on: push: branches: ["main", "dev"] pull_request: branches: ["main", "dev"] jobs: # Step 1: Code formatting and pre-commit validation (fast failure) code-format: name: Code formatting and pre-commit validation runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.10" # Use minimum version for consistency - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e .[dev] - name: Run pre-commit hooks uses: pre-commit/action@v3.0.1 with: extra_args: --all-files # Step 2: Basic build and test with minimum Python version (3.10) basic-build: name: Basic build (Python 3.10, Ubuntu) runs-on: ubuntu-latest needs: [code-format] # Only run after code formatting passes steps: - uses: actions/checkout@v5 - name: Set up Python 3.10 uses: actions/setup-python@v6 with: python-version: "3.10" - name: Cache pip dependencies uses: actions/cache@v4 with: path: ~/.cache/pip key: ubuntu-pip-3.10-${{ hashFiles('**/pyproject.toml') }} restore-keys: | ubuntu-pip-3.10- - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install pytest pip install -e .[dev] - name: Test with pytest run: | pytest -v # Step 3: ROS2 integration test test-with-ros2: name: ROS2 integration test runs-on: ubuntu-latest needs: [basic-build] # Only run after basic build passes steps: - uses: actions/checkout@v5 - name: Setup Miniconda uses: conda-incubator/setup-miniconda@v3 with: miniconda-version: "latest" channels: conda-forge,robostack-staging,defaults channel-priority: strict activate-environment: ros2-test-env python-version: "3.11.11" auto-activate-base: false auto-update-conda: false show-channel-urls: true - name: Install ROS2 and dependencies shell: bash -l {0} run: | # Install ROS2 core packages conda install -y \ ros-humble-ros-core \ ros-humble-std-msgs \ ros-humble-geometry-msgs - name: Install package and run tests shell: bash -l {0} run: | # Install our package with basic dependencies (not ros2 extra to avoid conflicts) pip install -e .[dev] # Run all tests with verbose output (ROS2 tests will be automatically included) python -c "import rclpy, rosidl_runtime_py; print('All ROS2 dependencies available')" pytest -v # Step 4: Security scan security: name: Security scan runs-on: ubuntu-latest needs: [basic-build] # Run in parallel with ROS2 test after basic build steps: - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.10" # Use minimum version for consistency - name: Install security tools run: | python -m pip install --upgrade pip pip install "safety>=3.0.0" "typer<0.12.0" "marshmallow<4.0.0" - name: Run safety security scan run: safety check --output json > safety-report.json - name: Upload security reports uses: actions/upload-artifact@v4 with: name: security-reports path: | safety-report.json if: always() # Step 5: Package build check package-build: name: Package build check runs-on: ubuntu-latest needs: [basic-build] # Run in parallel with other checks steps: - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.10" # Use minimum version for consistency - name: Install build dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build package run: python -m build - name: Check package run: twine check dist/* - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist/ # Step 6: Full matrix build (only after all basic checks pass) full-matrix-build: name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} needs: [test-with-ros2, security, package-build] # Wait for all prerequisite checks strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.10", "3.11", "3.12", "3.13"] exclude: # Skip the combination we already tested in basic-build - os: ubuntu-latest python-version: "3.10" steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Cache pip dependencies uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install flake8 pytest pip install -e .[dev] - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-line-length=200 --extend-ignore=E203,W503,F401,E402,E721,F841 --statistics - name: Type checking with mypy run: | mypy msgcenterpy --disable-error-code=unused-ignore - name: Test with pytest run: | pytest