# This workflow will upload a Python Package to PyPI when a release is created # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Upload PyPI package on: release: types: [published, edited] workflow_dispatch: inputs: test_pypi: description: "Publish to Test PyPI instead of PyPI" required: false default: false type: boolean permissions: contents: read 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: Run Safety CLI to check for vulnerabilities uses: pyupio/safety-action@v1 with: api-key: ${{ secrets.SAFETY_CHECK }} output-format: json args: --detailed-output --output-format json - name: Upload security reports uses: actions/upload-artifact@v4 with: name: security-reports path: | safety-report.json if: always() release-build: name: Build release distributions runs-on: ubuntu-latest needs: [test-with-ros2] 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 python -m pip install build twine - name: Verify version consistency if: github.event_name == 'release' && (github.event.action == 'published' || (github.event.action == 'edited' && !github.event.release.prerelease)) run: | # Install package first pip install -e . # Get package version (fail fast if not available) VERSION=$(python -c "import msgcenterpy; print(msgcenterpy.__version__)") # Handle both v0.0.3 and 0.0.3 tag formats RAW_TAG="${GITHUB_REF#refs/tags/}" if [[ "$RAW_TAG" == v* ]]; then TAG_VERSION="${RAW_TAG#v}" else TAG_VERSION="$RAW_TAG" fi echo "Package version: $VERSION" echo "Tag version: $TAG_VERSION" if [ "$VERSION" != "$TAG_VERSION" ]; then echo "❌ Version mismatch: package=$VERSION, tag=$TAG_VERSION" echo "Please ensure the package version matches the git tag" exit 1 fi echo "✅ Version verification passed: $VERSION" - name: Build release distributions run: | python -m build - name: Check package run: | twine check dist/* - name: Upload distributions uses: actions/upload-artifact@v4 with: name: release-dists path: dist/ pypi-publish: name: Publish to PyPI runs-on: ubuntu-latest needs: - release-build if: github.event_name == 'release' && !github.event.release.prerelease && github.event.inputs.test_pypi != 'true' && (github.event.action == 'published' || github.event.action == 'edited') permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write # Note: For enhanced security, consider configuring deployment environments # in your GitHub repository settings with protection rules steps: - name: Retrieve release distributions uses: actions/download-artifact@v5 with: name: release-dists path: dist/ - name: Publish release distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 test-pypi-publish: name: Publish to Test PyPI runs-on: ubuntu-latest needs: - release-build if: github.event.inputs.test_pypi == 'true' || (github.event_name == 'release' && github.event.release.prerelease && (github.event.action == 'published' || github.event.action == 'edited')) permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write # Note: For enhanced security, consider configuring deployment environments # in your GitHub repository settings with protection rules steps: - name: Retrieve release distributions uses: actions/download-artifact@v5 with: name: release-dists path: dist/ - name: Publish release distributions to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ create-github-release-assets: name: Add assets to GitHub release runs-on: ubuntu-latest needs: release-build if: github.event_name == 'release' && (github.event.action == 'published' || github.event.action == 'edited') permissions: contents: write # Need write access to upload release assets steps: - name: Retrieve release distributions uses: actions/download-artifact@v5 with: name: release-dists path: dist/ - name: Upload release assets uses: softprops/action-gh-release@v2 with: files: dist/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} post-publish: name: Post-publish tasks runs-on: ubuntu-latest needs: [pypi-publish, test-pypi-publish] if: always() && (needs.pypi-publish.result == 'success' || needs.test-pypi-publish.result == 'success') steps: - uses: actions/checkout@v5 - name: Create deployment summary run: | echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY echo "| Item | Status |" >> $GITHUB_STEP_SUMMARY echo "|------|--------|" >> $GITHUB_STEP_SUMMARY if [ "${{ needs.pypi-publish.result }}" = "success" ]; then echo "| PyPI | Published |" >> $GITHUB_STEP_SUMMARY elif [ "${{ needs.test-pypi-publish.result }}" = "success" ]; then echo "| Test PyPI | Published |" >> $GITHUB_STEP_SUMMARY fi echo "| GitHub Release | Assets uploaded |" >> $GITHUB_STEP_SUMMARY echo "| Version | ${{ github.event.release.tag_name || 'test' }} |" >> $GITHUB_STEP_SUMMARY