documentation

# This workflow will install Python dependencies, produce the sphinx documentation, and publish it to GitHub pages

name: documentation

on:
  push:
    branches:
      - 'master'
    tags:
      - '*'

jobs:
  documentation:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Python 3.10
      uses: actions/setup-python@v1
      with:
        python-version: '3.10'
    - name: Install dependencies
      run: |
        python -m pip install "pip<21.3"
        pip install .[develop]
    - name: Build the docs
      run: |
        sphinx-apidoc -f -o docs/source pisa
        cd docs && make html
    - name: Deploy to gh pages
      uses: peaceiris/actions-gh-pages@v3.9.2
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: docs/build/html


Python package

# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python package

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.8', '3.10']

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install "pip<21.3"
        pip install .
    - name: Lint with flake8
      run: |
        pip install flake8
        # 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-complexity=10 --max-line-length=127 --statistics
    - name: Test PISA example pipelines
      run: |
        pip install pytest
        PISA_FTYPE=fp32 PISA_TARGET=cpu MPLBACKEND=agg ./pisa_tests/test_example_pipelines.py -v
    - name: Test PISA imports and unit tests, double precision
      run: |
        pip install pytest
        PISA_FTYPE=fp64 PISA_TARGET=cpu MPLBACKEND=agg ./pisa_tests/run_unit_tests.py -v
    - name: Test PISA imports and unit tests, single precision
      run: |
        pip install pytest
        PISA_FTYPE=fp32 PISA_TARGET=cpu MPLBACKEND=agg ./pisa_tests/run_unit_tests.py -v


Docker Image

---
name: 'Docker Image'

on:
  workflow_dispatch:
  push:
    branches:
      - 'dev'
      - 'master'
    tags:
      - 'v*.*.*'
    paths-ignore:
      - .github/**
      - deploy/**
      - docker-test/**
      - docs/**
      - .dockerignore
      - .gitattributes
      - .gitignore
      - .lgtm.yml
      - .whitesource
      - .yarnrc
      - docker-compose.yml
      - docker-compose-test.yml
      - LICENSE
      - README.md
      - SECURITY.md

jobs:
  build-and-push-docker-image:
    name: Build Docker Image
    runs-on: ubuntu-latest

    steps:
      - name: Repository Checkout
        uses: actions/checkout@v2

      - name: Docker Image Metadata
        id: meta
        uses: docker/metadata-action@v3
        with:
          images: |
            powerdnsadmin/pda-legacy
          tags: |
            type=ref,event=tag
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}

      - name: QEMU Setup
        uses: docker/setup-qemu-action@v2

      - name: Docker Buildx Setup
        id: buildx
        uses: docker/setup-buildx-action@v1

      - name: Docker Hub Authentication
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME_V2 }}
          password: ${{ secrets.DOCKERHUB_TOKEN_V2 }}

      - name: Docker Image Build
        uses: docker/build-push-action@v4
        with:
          platforms: linux/amd64,linux/arm64
          context: ./
          file: ./docker/Dockerfile
          push: true
          tags: powerdnsadmin/pda-legacy:${{ github.ref_name }}

      - name: Docker Image Release Tagging
        uses: docker/build-push-action@v4
        if: ${{ startsWith(github.ref, 'refs/tags/v') }}
        with:
          platforms: linux/amd64,linux/arm64
          context: ./
          file: ./docker/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}


CodeQL

---
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
  workflow_dispatch:
  push:
    branches:
      - 'dev'
      - 'main'
      - 'master'
      - 'dependabot/**'
      - 'feature/**'
      - 'issue/**'
    paths-ignore:
      - .github/**
      - deploy/**
      - docker/**
      - docker-test/**
      - docs/**
      - powerdnsadmin/static/assets/**
      - powerdnsadmin/static/custom/css/**
      - powerdnsadmin/static/img/**
      - powerdnsadmin/swagger-spec.yaml
      - .dockerignore
      - .gitattributes
      - .gitignore
      - .lgtm.yml
      - .whitesource
      - .yarnrc
      - docker-compose.yml
      - docker-compose-test.yml
      - LICENSE
      - package.json
      - README.md
      - requirements.txt
      - SECURITY.md
      - yarn.lock
  pull_request:
    # The branches below must be a subset of the branches above
    branches:
      - 'dev'
      - 'main'
      - 'master'
      - 'dependabot/**'
      - 'feature/**'
      - 'issue/**'
    paths-ignore:
      - .github/**
      - deploy/**
      - docker/**
      - docker-test/**
      - docs/**
      - powerdnsadmin/static/assets/**
      - powerdnsadmin/static/custom/css/**
      - powerdnsadmin/static/img/**
      - powerdnsadmin/swagger-spec.yaml
      - .dockerignore
      - .gitattributes
      - .gitignore
      - .lgtm.yml
      - .whitesource
      - .yarnrc
      - docker-compose.yml
      - docker-compose-test.yml
      - LICENSE
      - package.json
      - README.md
      - requirements.txt
      - SECURITY.md
      - yarn.lock
  schedule:
    - cron: '45 2 * * 2'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'javascript', 'python' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
    - name: Checkout repository
      uses: actions/checkout@v3

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.

        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality

    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2

    # ℹ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

    #   If the Autobuild fails above, remove it and uncomment the following three lines.
    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

    # - run: |
    #   echo "Run, Build Application using script"
    #   ./location_of_script_within_repo/buildscript.sh

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2


Lock threads

---
# lock-threads (https://github.com/marketplace/actions/lock-threads)
name: 'Lock threads'

on:
  schedule:
    - cron: '0 3 * * *'
  workflow_dispatch:

permissions:
  issues: write
  pull-requests: write

jobs:
  lock:
    runs-on: ubuntu-latest
    steps:
      - uses: dessant/lock-threads@v3
        with:
          issue-inactive-days: 90
          pr-inactive-days: 30
          issue-lock-reason: 'resolved'
          exclude-any-issue-labels: 'bug / security-vulnerability, mod / announcement, mod / accepted, mod / reviewing, mod / testing'
          exclude-any-pr-labels: 'bug / security-vulnerability, mod / announcement, mod / accepted, mod / reviewing, mod / testing'


MegaLinter

---
# MegaLinter GitHub Action configuration file
# More info at https://megalinter.io
name: MegaLinter

on:
  workflow_dispatch:
  push:
    branches-ignore:
      - "*"
      - "dev"
      - "main"
      - "master"
      - "dependabot/**"
      - "feature/**"
      - "issues/**"
      - "release/**"

env: # Comment env block if you do not want to apply fixes
  # Apply linter fixes configuration
  APPLY_FIXES: all # When active, APPLY_FIXES must also be defined as environment variable (in github/workflows/mega-linter.yml or other CI tool)
  APPLY_FIXES_EVENT: all # Decide which event triggers application of fixes in a commit or a PR (pull_request, push, all)
  APPLY_FIXES_MODE: pull_request # If APPLY_FIXES is used, defines if the fixes are directly committed (commit) or posted in a PR (pull_request)

concurrency:
  group: ${{ github.ref }}-${{ github.workflow }}
  cancel-in-progress: true

jobs:
  build:
    name: MegaLinter
    runs-on: ubuntu-latest
    steps:
      # Git Checkout
      - name: Checkout Code
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}

      # MegaLinter
      - name: MegaLinter
        id: ml
        # You can override MegaLinter flavor used to have faster performances
        # More info at https://megalinter.io/flavors/
        uses: oxsecurity/megalinter@v6
        env:
          # All available variables are described in documentation
          # https://megalinter.io/configuration/
          VALIDATE_ALL_CODEBASE: true # Validates all source when push on main, else just the git diff with main. Override with true if you always want to lint all sources
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PAT: ${{ secrets.PAT }}
          # ADD YOUR CUSTOM ENV VARIABLES HERE OR DEFINE THEM IN A FILE .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY
          # DISABLE: COPYPASTE,SPELL # Uncomment to disable copy-paste and spell checks

      # Upload MegaLinter artifacts
      - name: Archive production artifacts
        if: ${{ success() }} || ${{ failure() }}
        uses: actions/upload-artifact@v3
        with:
          name: MegaLinter reports
          path: |
            megalinter-reports
            mega-linter.log

      # Create pull request if applicable (for now works only on PR from same repository, not from forks)
      - name: Create PR with applied fixes
        id: cpr
        if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
        uses: peter-evans/create-pull-request@v4
        with:
          token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
          commit-message: "[MegaLinter] Apply linters automatic fixes"
          title: "[MegaLinter] Apply linters automatic fixes"
          labels: bot

      - name: Create PR output
        if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
        run: |
          echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
          echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"

      # Push new commit if applicable (for now works only on PR from same repository, not from forks)
      - name: Prepare commit
        if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/main' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
        run: sudo chown -Rc $UID .git/
      - name: Commit and push applied linter fixes
        if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/main' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}
          commit_message: "[MegaLinter] Apply linters fixes"


Close Stale Threads

# close-stale-issues (https://github.com/marketplace/actions/close-stale-issues)
name: 'Close Stale Threads'

on:
  schedule:
    - cron: '0 4 * * *'
  workflow_dispatch:

permissions:
  issues: write
  pull-requests: write

jobs:
  stale:

    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v6
        with:
          close-issue-message: >
            This issue has been automatically closed due to lack of activity. In an
            effort to reduce noise, please do not comment any further. Note that the
            core maintainers may elect to reopen this issue at a later date if deemed
            necessary.
          close-pr-message: >
            This PR has been automatically closed due to lack of activity.
          days-before-stale: 90
          days-before-close: 30
          exempt-issue-labels: 'bug / security-vulnerability, mod / announcement, mod / accepted, mod / reviewing, mod / testing'
          operations-per-run: 100
          remove-stale-when-updated: false
          stale-issue-label: 'mod / stale'
          stale-issue-message: >
            This issue has been automatically marked as stale because it has not had
            recent activity. It will be closed if no further activity occurs. PDA
            is governed by a small group of core maintainers which means not all opened
            issues may receive direct feedback. **Do not** attempt to circumvent this
            process by "bumping" the issue; doing so will result in its immediate closure
            and you may be barred from participating in any future discussions. Please see our
            [Contribution Guide](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/CONTRIBUTING.md).
          stale-pr-label: 'mod / stale'
          stale-pr-message: >
            This PR has been automatically marked as stale because it has not had
            recent activity. It will be closed automatically if no further action is
            taken. Please see our
            [Contribution Guide](https://github.com/PowerDNS-Admin/PowerDNS-Admin/blob/master/docs/CONTRIBUTING.md).


Lint

name: Lint

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: 3.8

      - name: Run black, flake8 and isort
        uses: pre-commit/action@v3.0.1

      - name: Validate citation file
        shell: bash
        run: |
          python -m pip install cffconvert
          cffconvert --validate
          cffconvert -f bibtex
          cffconvert -f apalike


Build and publish

name: Build and publish

on:
  push:
    branches:
      - main  # just build the sdist & wheel, skip release
    tags:
      - "*"
  pull_request:  # also build on PRs touching this file
    paths:
      - ".github/workflows/release.yml"
      - "ci/*"
      - "MANIFEST.in"
      - "pyproject.toml"
      - "setup.py"
  schedule:
    # in addition run weekly for nightly upload in case no other commits happened
    - cron: '34 2 * * 0'

jobs:
  build_sdist:
    name: Build sdist
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Build a source tarball
        run: |
          python -m pip install --upgrade pip
          python -m pip install build twine
          python -m build --sdist
          twine check --strict dist/*

      - uses: actions/upload-artifact@v4
        with:
          name: release-sdist
          path: ./dist/*.tar.gz
          retention-days: 30

  build_wheels:
    name: Build ${{ matrix.arch }} wheels on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    env:
      GEOS_VERSION: "3.12.1"
    strategy:
      fail-fast: false
      matrix:
        include:
        - os: ubuntu-20.04
          arch: x86_64
        # Numpy no longer builds i686 packages
        # - os: ubuntu-20.04
        #   arch: i686
        # The aarch64 build has been transferred to Travis
        # - os: ubuntu-20.04
        #   arch: aarch64
        #   qemu_platform: arm64
        # Note: Numpy doesn't have ppc64le & s390x wheels
        # Also, some GEOS tests fail on s390x.
        - os: windows-2019
          arch: x86
          msvc_arch: x86
        - os: windows-2019
          arch: AMD64
          msvc_arch: x64
        - os: macos-11
          arch: x86_64
          cmake_osx_architectures: x86_64
        - os: macos-14
          arch: arm64
          cmake_osx_architectures: arm64

    steps:
      - name: Checkout source
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Cache GEOS build
        uses: actions/cache@v4
        with:
          path: ${{ runner.temp }}/geos-${{ env.GEOS_VERSION }}
          key: ${{ matrix.os }}-${{ matrix.arch }}-${{ env.GEOS_VERSION }}-${{ hashFiles('ci/*') }}

      - name: Add GEOS LICENSE
        run: |
          cp ci/wheelbuilder/LICENSE_GEOS .
        shell: bash

      - name: Add MSVC LICENSE
        run: |
          cp ci/wheelbuilder/LICENSE_win32 .
        shell: bash
        if: ${{ matrix.os == 'windows-2019' }}

      # - name: Set up QEMU
      #   uses: docker/setup-qemu-action@v1
      #   with:
      #     platforms: ${{ matrix.qemu_platform }}
      #   if: ${{ matrix.qemu_platform }}

      - name: Activate MSVC
        uses: ilammy/msvc-dev-cmd@v1
        with:
          arch: ${{ matrix.msvc_arch }}
        if: ${{ matrix.msvc_arch }}

      - name: Build wheels
        uses: pypa/cibuildwheel@v2.19.1
        env:
          CIBW_ARCHS: ${{ matrix.arch }}
          CIBW_SKIP: cp36-* pp* *musllinux* *-manylinux_i686
          CIBW_TEST_SKIP: "cp38-macosx_arm64"
          CIBW_ENVIRONMENT_LINUX:
            GEOS_VERSION=${{ env.GEOS_VERSION }}
            GEOS_INSTALL=/host${{ runner.temp }}/geos-${{ env.GEOS_VERSION }}
            GEOS_CONFIG=/host${{ runner.temp }}/geos-${{ env.GEOS_VERSION }}/bin/geos-config
            LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/host${{ runner.temp }}/geos-${{ env.GEOS_VERSION }}/lib
          CIBW_ENVIRONMENT_MACOS:
            GEOS_INSTALL=${{ runner.temp }}/geos-${{ env.GEOS_VERSION }}
            GEOS_CONFIG=${{ runner.temp }}/geos-${{ env.GEOS_VERSION }}/bin/geos-config
            MACOSX_DEPLOYMENT_TARGET=10.9
            CMAKE_OSX_ARCHITECTURES='${{ matrix.cmake_osx_architectures }}'
          CIBW_ENVIRONMENT_WINDOWS:
            GEOS_INSTALL='${{ runner.temp }}\geos-${{ env.GEOS_VERSION }}'
            GEOS_LIBRARY_PATH='${{ runner.temp }}\geos-${{ env.GEOS_VERSION }}\lib'
            GEOS_INCLUDE_PATH='${{ runner.temp }}\geos-${{ env.GEOS_VERSION }}\include'
          CIBW_BEFORE_ALL: ./ci/install_geos.sh
          CIBW_BEFORE_ALL_WINDOWS: ci\install_geos.cmd
          # TEMP don't use automated/isolated build environment, but manually
          # install build dependencies so we can build with numpy 2.0
          # once numpy 2.0 is out, this can be removed again
          CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation"
          CIBW_BEFORE_BUILD: pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy setuptools cython wheel
          CIBW_BEFORE_BUILD_WINDOWS:
            pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy setuptools cython wheel
            pip install delvewheel
          CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: delvewheel repair --add-path ${{ runner.temp }}\geos-${{ env.GEOS_VERSION }}\bin -w {dest_dir} {wheel}
          CIBW_TEST_REQUIRES: pytest
          CIBW_TEST_COMMAND: pytest --pyargs shapely.tests

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: release-${{ matrix.os }}-${{ matrix.arch }}
          path: ./wheelhouse/*.whl
          retention-days: 5

  nightly_upload:
    name: Upload nightly wheels
    needs: [build_wheels]
    runs-on: ubuntu-latest
    if: github.repository == 'shapely/shapely' && github.ref == 'refs/heads/main'
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: release-*
          merge-multiple: true
          path: dist
      - name: Upload wheels to Anaconda Cloud
        uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0
        with:
          artifacts_path: dist
          anaconda_nightly_upload_token: ${{secrets.ANACONDA_ORG_UPLOAD_TOKEN}}

  publish:
    name: Publish on GitHub and PyPI
    needs: [build_wheels, build_sdist]
    runs-on: ubuntu-latest
    # release on every tag
    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: release-*
          merge-multiple: true
          path: dist

      - name: Create GitHub Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
          draft: false
          prerelease: false

      - name: Get Asset name
        run: |
          export PKG=$(ls dist/ | grep tar)
          set -- $PKG
          echo "name=$1" >> $GITHUB_ENV

      - name: Upload Release Asset (sdist) to GitHub
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: dist/${{ env.name }}
          asset_name: ${{ env.name }}
          asset_content_type: application/zip

      - name: Upload Release Assets to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.pypi_password }}
          skip_existing: true
          # To test: repository_url: https://test.pypi.org/legacy/


Tests

name: Tests

on: [push, pull_request]

jobs:
  test:
    name: ${{ matrix.os }}-${{ matrix.architecture }} Py${{ matrix.python }} GEOS ${{ matrix.geos }}
    runs-on: ${{ matrix.os }}
    defaults:
      run:
        shell: bash
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-13, windows-2019]
        architecture: [x64]
        geos: [3.8.4, 3.9.5, 3.10.6, 3.11.3, 3.12.1, main]
        include:
          # 2019
          - python: 3.8
            geos: 3.8.4
            numpy: 1.17.3
          # 2020
          - python: 3.9
            geos: 3.9.5
            numpy: 1.19.5
          # 2021
          - python: "3.10"
            geos: 3.10.6
            numpy: 1.21.3
          # 2022
          - python: "3.11"
            geos: 3.11.3
            numpy: 1.23.4
          # 2023
          - python: "3.12"
            geos: 3.12.1
            numpy: 1.26.2
            matplotlib: true
            doctest: true
            # extra ignore for dateutil Python 3.12 warning fixed upstream (waiting on release 2.8.3+)
            extra_pytest_args: "-W error -W ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning" # error on warnings
          # dev
          - python: "3.12"
            geos: main
            # extra ignore for dateutil Python 3.12 warning fixed upstream (waiting on release 2.8.3+)
            extra_pytest_args: "-W error -W ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning" # error on warnings
          # enable two 32-bit windows builds:
          - os: windows-2019
            architecture: x86
            python: 3.8
            geos: 3.8.4
            numpy: 1.16.2
          - os: windows-2019
            architecture: x86
            python: "3.11"
            geos: 3.12.1
            numpy: 1.24.4
          # pypy (use explicit ubuntu version to not overwrite existing ubuntu-latest + geos 3.11.0 build)
          - os: ubuntu-22.04
            python: "pypy3.8"
            geos: 3.12.1
            numpy: 1.24.4

    env:
      GEOS_VERSION: ${{ matrix.geos }}
      GEOS_VERSION_SPEC: ${{ matrix.geos }}
      GEOS_INSTALL: ${{ github.workspace }}/geosinstall/geos-${{ matrix.geos }}
      GEOS_BUILD: ${{ github.workspace }}/geosbuild

    steps:
      - name: Correct slashes in GEOS_INSTALL (Windows)
        run: |
          echo 'GEOS_INSTALL=${{ github.workspace }}\geosinstall\geos-${{ matrix.geos }}' >> $GITHUB_ENV
          echo 'GEOS_BUILD=${{ github.workspace }}\geosbuild' >> $GITHUB_ENV
        if: ${{ matrix.os == 'windows-2019' }}

      - name: Checkout Shapely
        uses: actions/checkout@v4

      - name: Checkout GEOS (main)
        uses: actions/checkout@v4
        with:
          repository: libgeos/geos
          ref: main
          path: ${{ env.GEOS_BUILD }}
        if: ${{ matrix.geos == 'main' }}

      - name: Put the latest commit hash in the cache token for GEOS main
        run: |
          echo "GEOS_VERSION_SPEC=$(git rev-parse HEAD)" >> $GITHUB_ENV
        working-directory: ${{ env.GEOS_BUILD }}
        if: ${{ matrix.geos == 'main' }}

      - name: Set up Python ${{ matrix.python }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
          architecture: ${{ matrix.architecture }}
          allow-prereleases: true

      - name: Cache GEOS and pip packages
        uses: actions/cache@v4
        with:
          key: ${{ matrix.os }}-${{ matrix.architecture }}-geos-${{ env.GEOS_VERSION_SPEC }}-${{ hashFiles('ci/install_geos.sh') }}
          path: |
            ~/.cache/pip
            ${{ github.workspace }}/geosinstall

      - name: Activate MSVC
        uses: ilammy/msvc-dev-cmd@v1
        with:
          arch: ${{ matrix.architecture }}
        if: ${{ matrix.os == 'windows-2019' }}

      - name: Install GEOS
        run: |
          bash ci/install_geos.sh

      - name: Install python dependencies
        run: |
          python -m pip install --disable-pip-version-check --upgrade pip
          pip install --upgrade wheel setuptools
          if [ -z "${{ matrix.numpy }}" ]; then
            pip install --upgrade --pre Cython pytest pytest-cov coveralls;
            pip install --upgrade --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy
          else
            pip install --upgrade Cython numpy==${{ matrix.numpy }} pytest pytest-cov coveralls;
          fi
          if [ -n "${{ matrix.matplotlib }}" ]; then
            pip install matplotlib
          fi
          if [ -n "${{ matrix.doctest }}" ]; then
            pip install pyproj
          fi
          pip list

      - name: Set environment variables (Linux)
        run: |
          echo "${{ env.GEOS_INSTALL }}/bin" >> $GITHUB_PATH
          echo "LD_LIBRARY_PATH=${{ env.GEOS_INSTALL }}/lib" >> $GITHUB_ENV
        if: ${{ startsWith(matrix.os, 'ubuntu') }}

      - name: Set environment variables (OSX)
        run: |
          echo "${{ env.GEOS_INSTALL }}/bin" >> $GITHUB_PATH
          echo "LDFLAGS=-Wl,-rpath,${{ env.GEOS_INSTALL }}/lib" >> $GITHUB_ENV
        if: ${{ matrix.os == 'macos-13' }}

      # Windows requires special treatment:
      # - geos-config does not exist, so we specify include and library paths
      # - Python >=3.8 ignores the PATH for finding DLLs, so we copy them into the package
      - name: Set environment variables + copy DLLs (Windows)
        run: |
          cp geosinstall/geos-${{ matrix.geos }}/bin/*.dll shapely
          echo 'GEOS_LIBRARY_PATH=${{ env.GEOS_INSTALL }}\lib' >> $GITHUB_ENV
          echo 'GEOS_INCLUDE_PATH=${{ env.GEOS_INSTALL }}\include' >> $GITHUB_ENV
        if: ${{ matrix.os == 'windows-2019' }}

      - name: Build and install Shapely
        # for the numpy nightly build, we temporarily need to build without
        # the standard build isolation based on build-system.requires, to
        # ensure we build against numpy nightly (numpy 2.0 compat)
        run: |
          if [ -z "${{ matrix.numpy }}" ]; then
            pip install -e . --no-build-isolation
          else
            pip install -e .
          fi

      - name: Overview of the Python environment (pip list)
        run: pip list

      - name: Run tests
        # Enable this if we have failures on GEOS main
        # continue-on-error: ${{ matrix.geos == 'main' }}
        run: |
          python -c "import shapely; print(f'GEOS version: {shapely.geos_version_string}')"
          pytest shapely/tests -r a --cov --cov-report term-missing ${{ matrix.extra_pytest_args }}

      # Only run doctests on 1 runner (because of typographic differences in doctest results)
      - name: Run doctests
        if: ${{ matrix.os == 'ubuntu-latest' && matrix.doctest }}
        run: |
          python -m pytest --doctest-modules docs/manual.rst

      - name: Run doctests (part 2)
        if: ${{ matrix.os == 'ubuntu-latest' && matrix.doctest }}
        run: |
          pytest --doctest-modules shapely --ignore=shapely/tests

      - name: Upload coverage
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COVERALLS_PARALLEL: true
        shell: bash
        run: |
          coveralls --service=github || echo "!! intermittent coveralls failure"

  coveralls:
    name: Indicate completion to coveralls.io
    needs: test
    runs-on: ubuntu-latest
    container: python:3-slim
    steps:
      - name: Finished
        run: |
          pip3 install --upgrade coveralls
          coveralls --finish || echo "!! intermittent coveralls failure"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


Build documentation

name: Build documentation

on:
  pull_request:
    branches:
      - master
  push:
    branches:
      - master

jobs:
  build-matrix:
    runs-on: ubuntu-20.04
    steps:
      - name: Generate version matrix
        id: build-matrix
        run: echo "versions=['stable', 'latest']" >> "$GITHUB_OUTPUT"
    outputs:
      versions: ${{ steps.build-matrix.outputs.versions }}

  docs:
    runs-on: ubuntu-20.04
    needs: build-matrix
    strategy:
      # Make sure we never build docs in parallel in case we deploy new
      # versions to avoid potential race conditions while pushing to gh pages.
      max-parallel: 1
      matrix:
        version: ${{ fromJSON(needs.build-matrix.outputs.versions) }}
    steps:
      - name: Clone repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Set up python 3.8
        uses: actions/setup-python@v4
        with:
          python-version: 3.8
          cache: pip
          cache-dependency-path: pyproject.toml

      - name: Clone repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Checkout target
        id: checkout
        run: |
          if ${{ matrix.version == 'latest' }}; then
            sha=$(git rev-parse --short ${{ github.sha }})
            git checkout ${{ github.sha }}
          elif ${{ matrix.version == 'stable' }}; then
            tag=$(git tag | sort -V | tail -1)
            git checkout tags/$tag
          else
            git checkout ${{ matrix.version }}
          fi

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y pandoc
          pip install -e ".[all,ci,docs]"

      - name: Run doctests
        run: sphinx-build -b doctest docs $(mktemp -d)

      - name: Build docs
        run: |
          sphinx-build \
            -b html \
            -D doc_version="${{ matrix.version }}" \
            -D doc_versions="${{ join(fromJSON(needs.build-matrix.outputs.versions)) }}" \
            docs \
            website/docs/${{ matrix.version }}

      - name: Publish documentation
        if: ${{ github.ref == 'refs/heads/master' }}
        uses: peaceiris/actions-gh-pages@v3
        with:
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          external_repository: pymanopt/pymanopt.github.io
          publish_branch: main
          publish_dir: website/docs/${{ matrix.version }}
          destination_dir: docs/${{ matrix.version }}
          user_name: 'github-actions[bot]'
          user_email: 'github-actions[bot]@users.noreply.github.com'
          commit_message: ${{ github.event.head_commit.message }}


Build release version

name: Build release version

on:
  push:
    branches:
      - master
    tags:
      - '*.*.*'
  pull_request:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up python 3.8
        uses: actions/setup-python@v4
        with:
          python-version: 3.8
          cache: pip
          cache-dependency-path: pyproject.toml

      - name: Checkout
        uses: actions/checkout@v2

      - name: Install dependencies
        run: pip install -e ".[ci]"

      - name: Build release artifacts
        run: python -m build

      - name: Verify tag name
        uses: nowsprinting/check-version-format-action@v3
        id: tag

      - name: Publish tag to pypi
        if: >
          startsWith(github.ref, 'refs/tags/') &&
          steps.tag.outputs.is_valid == 'true'
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          password: ${{ secrets.PYPI_API_TOKEN }}


CodeQL

name: "CodeQL"

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]
  schedule:
    - cron: "4 15 * * 4"

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ python ]

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: ${{ matrix.language }}
          queries: +security-and-quality

      - name: Autobuild
        uses: github/codeql-action/autobuild@v2

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2
        with:
          category: "/language:${{ matrix.language }}"


Run linting

name: Run linting

on:
  pull_request:
    branches:
      - master

jobs:
  linting:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up python 3.9
        uses: actions/setup-python@v4
        with:
          python-version: 3.9
          cache: pip
          cache-dependency-path: pyproject.toml

      - name: Checkout
        uses: actions/checkout@v2

      - name: Install dependencies
        run: pip install -e ".[dev,ci]"

      - name: Run linting
        run: |
          isort --check .
          black --check .
          flake8


Run test suite

name: Run test suite

on:
  pull_request:
    branches:
      - master
  push:

jobs:
  tests:
    runs-on: ubuntu-20.04
    strategy:
      matrix:
        python-version:
          - "3.8"
          - "3.9"
          - "3.10"
          - "3.11"
          - "3.12"
          - "3.x"
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip
          cache-dependency-path: pyproject.toml

      - name: Checkout
        uses: actions/checkout@v2

      - name: Install dependencies
        run: pip install -e ".[all,ci]"

      - name: Run tests
        run: |
          pytest -v --cov-report= --cov=src/pymanopt tests/
          coverage lcov -o coverage/lcov.info

      - name: Collect coverage
        uses: coverallsapp/github-action@master
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          flag-name: ${{ matrix.python-version }}
          parallel: true

  finish:
    needs: tests
    runs-on: ubuntu-20.04
    steps:
      - name: Upload coverage
        uses: coverallsapp/github-action@master
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          parallel-finished: true


Build and publish to Docker Hub

name: Build and publish to Docker Hub
on:
  release:
    # job will automatically run after a new "release" is create on github.
    types: [created]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:
    inputs:
        dry_run:
          description: 'If true, will not push the built images to docker hub.'
          required: false
          default: 'false'

jobs:
  # this job will build, test and (potentially) push the docker images to docker hub
  #
  # BUILD PHASE:
  # - will auto tag the image according to the release tag / `git describe`.
  #
  # TEST PHASE:
  # - will run an e2e test with a modified docker compose.
  # - queries OPA data to check its state matches an expected value.
  # - state will match only if OPAL client successfully synced to OPAL server.
  # - outputs the docker compose logs to more easily investigate errors.
  #
  # PUSH PHASE:
  # - Runs only if test phase completes with no errors.
  # - Pushes images (built at BUILD PHASE) to docker hub.
  docker_build_and_publish:
    runs-on: ubuntu-latest
    steps:
      # BUILD PHASE
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to DockerHub
        if: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true') }}
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Get version tag from github release
        if: github.event_name == 'release' && github.event.action == 'created'
        run: |
          echo "opal_version_tag=${{ github.event.release.tag_name }}" >> $GITHUB_ENV

      - name: Get version tag from git history
        if: ${{ !(github.event_name == 'release' && github.event.action == 'created') }}
        run: |
          echo "opal_version_tag=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV

      - name: Echo version tag
        run: |
          echo "The version tag that will be published to docker hub is: ${{ env.opal_version_tag }}"

      - name: Build client for testing
        id: build_client
        uses: docker/build-push-action@v4
        with:
          file: docker/Dockerfile
          push: false
          target: client
          cache-from: type=registry,ref=permitio/opal-client:latest
          cache-to: type=inline
          load: true
          tags: |
            permitio/opal-client:test

      - name: Build client-standalone for testing
        id: build_client_standalone
        uses: docker/build-push-action@v4
        with:
          file: docker/Dockerfile
          push: false
          target: client-standalone
          cache-from: type=registry,ref=permitio/opal-client-standalone:latest
          cache-to: type=inline
          load: true
          tags: |
            permitio/opal-client-standalone:test

      - name: Build server for testing
        id: build_server
        uses: docker/build-push-action@v4
        with:
          file: docker/Dockerfile
          push: false
          target: server
          cache-from: type=registry,ref=permitio/opal-server:latest
          cache-to: type=inline
          load: true
          tags: |
            permitio/opal-server:test

      # TEST PHASE
      - name: Create modified docker compose file
        run: sed 's/:latest/:test/g' docker/docker-compose-example.yml > docker/docker-compose-test.yml

      - name: Bring up stack
        run: docker-compose -f docker/docker-compose-test.yml up -d

      - name: Check if OPA is healthy
        run: ./scripts/wait-for.sh -t 60 http://localhost:8181/v1/data/users -- sleep 10 && curl -s "http://localhost:8181/v1/data/users" | jq '.result.bob.location.country == "US"'

      - name: Output container logs
        run: docker-compose -f docker/docker-compose-test.yml logs

      # PUSH PHASE
      - name: Output local docker images
        run: docker image ls --digests | grep opal

      # pushes the *same* docker images that were previously tested as part of e2e sanity test.
      # each image is pushed with the versioned tag first, if it succeeds the image is pushed with the latest tag as well.
      - name: Build & Push client
        if: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true') }}
        id: build_push_client
        uses: docker/build-push-action@v4
        with:
          file: docker/Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          target: client
          cache-from: type=registry,ref=permitio/opal-client:latest
          cache-to: type=inline
          tags: |
            permitio/opal-client:latest
            permitio/opal-client:${{ env.opal_version_tag }}

      - name: Build client-standalone
        if: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true') }}
        id: build_push_client_standalone
        uses: docker/build-push-action@v4
        with:
          file: docker/Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          target: client-standalone
          cache-from: type=registry,ref=permitio/opal-client-standalone:latest
          cache-to: type=inline
          tags: |
            permitio/opal-client-standalone:latest
            permitio/opal-client-standalone:${{ env.opal_version_tag }}

      - name: Build server
        if: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true') }}
        id: build_push_server
        uses: docker/build-push-action@v4
        with:
          file: docker/Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          target: server
          cache-from: type=registry,ref=permitio/opal-server:latest
          cache-to: type=inline
          tags: |
            permitio/opal-server:latest
            permitio/opal-server:${{ env.opal_version_tag }}


pre-commit

name: pre-commit

on:
  pull_request:
  push:
    branches: [master, main]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-python@v4
      with:
        python-version: 3.x
    - uses: pre-commit/action@v3.0.0


Tests

# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Tests

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  build:
    services:
      redis:
        image: redis
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11", "3.12"]

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        python -m pip install flake8
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
        if [ -f ./tests/requirements.txt ]; then pip install -r ./tests/requirements.txt; fi
    - 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-complexity=10 --max-line-length=127 --statistics
    - name: Test with pytest
      run: |
        pytest

  test-docker:
    runs-on: ubuntu-latest
    steps:
      # BUILD PHASE
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Build client
        id: build_client
        uses: docker/build-push-action@v2
        with:
          file: docker/Dockerfile
          push: false
          target: client
          cache-from: type=registry,ref=permitio/opal-client:latest
          cache-to: type=inline
          load: true
          tags: |
            permitio/opal-client:test

      - name: Build server
        id: build_server
        uses: docker/build-push-action@v2
        with:
          file: docker/Dockerfile
          push: false
          target: server
          cache-from: type=registry,ref=permitio/opal-server:latest
          cache-to: type=inline
          load: true
          tags: |
            permitio/opal-server:test

      # TEST PHASE
      - name: Create modified docker compose file
        run: sed 's/:latest/:test/g' docker/docker-compose-with-callbacks.yml > docker/docker-compose-test.yml

      - name: Bring up stack
        run: docker-compose -f docker/docker-compose-test.yml up -d

      - name: Check if OPA is healthy
        run: ./scripts/wait-for.sh -t 2 http://localhost:8181/v1/data/users -- sleep 10 && curl -s "http://localhost:8181/v1/data/users" | jq '.result.bob.location.country == "US"'

      - name: Output container logs
        run: docker-compose -f docker/docker-compose-test.yml logs

      - name: check if opal-client was brought up
        run: |
          docker-compose -f docker/docker-compose-test.yml logs opal_client | grep "Connected to PubSub server"
          docker-compose -f docker/docker-compose-test.yml logs opal_client | grep "Got policy bundle"


check-changelog

name: check-changelog

on:
  pull_request:
    branches: [main]

jobs:
  check-changelog-entry:
    if: "!contains(github.event.pull_request.labels.*.name, 'no-changelog-entry-needed')"
    runs-on: ubuntu-latest
    steps:
      - name: Checkout PR branch
        uses: actions/checkout@v3
      - name: Find changelog entry
        env:
          PR_NUMBER: ${{ github.event.number }}
        run: |
          if compgen -G "docs/changes/$PR_NUMBER.*.rst"; then
            echo "changelog exists"
          else
            echo "changelog entry not found"
            exit 1
          fi


Docs checks

name: Docs checks

on:
  push:
    branches:
    - main
  pull_request:
  schedule:
    # run every Monday at 6am UTC
    - cron: '0 6 * * 1'

env:
  SETUP_XVFB: True  # avoid issues if mpl tries to open a GUI window
  TOXARGS: '-v'

jobs:  # This job is used to check that the documentation builds correctly.
  ci-tests:
    name: ${{ matrix.tox_env }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            python: '3.10'
            tox_env: 'build_docs'
          - os: ubuntu-latest
            python: '3.10'
            tox_env: 'linkcheck'
    steps:
      - name: Check out repository
        uses: actions/checkout@v3
        with:
          submodules: true
      - name: Check out that no sensitive environment variable is shared
        run: env
      - name: Set up Python ${{ matrix.python }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python }}
      - name: Install base dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install tox
      - name: Install system dependencies
        run: sudo apt-get -y install graphviz pandoc
      - name: Print Python, pip, setuptools, and tox versions
        run: |
          python -c "import sys; print(f'Python {sys.version}')"
          python -c "import pip; print(f'pip {pip.__version__}')"
          python -c "import setuptools; print(f'setuptools {setuptools.__version__}')"
          python -c "import tox; print(f'tox {tox.__version__}')"
      - name: Run tests
        run: tox -e ${{ matrix.tox_env }}


CI Tests

name: CI Tests

on:
  push:
    branches:
    - main
  pull_request:
  schedule:
    # run every Monday at 6am UTC
    - cron: '0 6 * * 1'

env:
  SETUP_XVFB: True  # avoid issues if mpl tries to open a GUI window
  TOXARGS: '-v'

jobs:

  check_commit:
    # Check the commit message for the presence of keywords that indicate
    # that the CI tests should be skipped, in favor of running doc builds only.
    # Messages like [docs only], [docs-only], or [skip-tests] will skip
    # Only the CI part of the workflow, not the doc build.
    # [skip ci], [ci skip] etc. are instead handled by GitHub itself and will skip
    # the entire workflow.
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.event.pull_request.head.ref }}
          repository: ${{github.event.pull_request.head.repo.full_name}}

      # Found this solution at
      # https://monadical.com/posts/filters-github-actions.html#Case-2-Pull-request
      - name: check if message indicates that tests should be skipped
        id: check_commit
        run: |
          message=$(git log -1 --pretty=format:'%B')
          re="\[(docs.only|skip-tests).*\]"
          if [[ $message =~ $re ]]; then
            echo "match=true" >> $GITHUB_OUTPUT
            echo "$message -> Match is true"
          else
            echo "$message -> Match is false"
          fi
    outputs:
      match: ${{ steps.check_commit.outputs.match }}

  ci-tests:
    needs: check_commit
    if: ${{ needs.check_commit.outputs.match != 'true' }}
    # name: ${{ matrix.os }}, ${{ matrix.tox_env }}
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.experimental }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # A large number of test environments are defined in tox.ini. In CI,
          # we select only a few, in order to conserve resources. If you add
          # an additional environment, please include a comment to indicate
          # why it is useful.
          - name: Black test
            os: ubuntu-latest
            python: '3.10'
            tox_env: 'black'
            experimental: false

          # Basic tests on the oldest & newest supported versions of Python,
          # recording coverage data for the latter.
          - name: Py3.8 with old Astropy version
            os: ubuntu-latest
            python: '3.8'
            tox_env: 'py38-test-astropy4-cov'
            experimental: false

          - name: Linux, Py3.10 with coverage
            os: ubuntu-latest
            python: '3.10'
            tox_env: 'py310-test-cov'
            experimental: false

          # Basic tests on alternative operating systems.
          - name: Windows, Py3.11 with coverage
            os: windows-latest
            python: '3.11'
            tox_env: 'py311-test-cov'
            experimental: false

          - name: Mac OS M1, Py3.11
            os: macos-14
            python: '3.11'
            tox_env: 'py311-test'
            experimental: false

          - name: Mac OS, Py3.11
            os: macos-latest
            python: '3.11'
            tox_env: 'py311-test'
            experimental: false

          # Test with all optional dependencies installed.
          - name: Linux, Py3.11 all dependencies and coverage
            os: ubuntu-latest
            python: '3.11'
            tox_env: 'py311-test-alldeps-cov'
            use_remote_data: true
            experimental: false

          # Test 3.12 with basic optional dependencies installed.
          - name: Linux, Py3.12 basic dependencies and coverage
            os: ubuntu-latest
            python: '3.12'
            tox_env: 'py312-test-cov'
            use_remote_data: true
            experimental: true

          # Development version of dependencies
          - name: Linux, Py3.11 with dev versions of dependencies
            os: ubuntu-latest
            python: '3.11'
            tox_env: 'py311-test-devdeps-cov'
            experimental: false

          # Test with all optional dependencies installed.
          - name: Slow tests on Linux, Py3.11, all deps and coverage
            os: ubuntu-latest
            python: '3.11'
            tox_env: 'py311-test-alldeps-cov'
            use_remote_data: true
            experimental: false
            slow: true

          # Test with all optional dependencies installed.
          - name: Slow tests on Linux, Py3.11, basic deps and coverage
            os: ubuntu-latest
            python: '3.11'
            tox_env: 'py311-test-cov'
            use_remote_data: true
            experimental: false
            slow: true

    steps:
    - name: Check out repository
      uses: actions/checkout@v3
      with:
        submodules: true
    - name: Check out that no sensitive environment variable is shared
      run: env
    - name: Set up Python ${{ matrix.python }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python }}
    - name: Install base dependencies
      run: |
        python -m pip install --upgrade pip setuptools wheel
        python -m pip install tox
    - name: Install system dependencies
      if: "endsWith(matrix.tox_env, 'build_docs')"
      run: sudo apt-get -y install graphviz pandoc
    - name: Print Python, pip, setuptools, and tox versions
      run: |
        python -c "import sys; print(f'Python {sys.version}')"
        python -c "import pip; print(f'pip {pip.__version__}')"
        python -c "import setuptools; print(f'setuptools {setuptools.__version__}')"
        python -c "import tox; print(f'tox {tox.__version__}')"
    - name: Run quick tests
      if: "(! matrix.use_remote_data) && (! matrix.slow)"
      run: tox -e ${{ matrix.tox_env }} --
    - name: Run quick tests with remote data
      if: "matrix.use_remote_data && (! matrix.slow)"
      run: |
        pip install pytest-remotedata
        tox -e ${{ matrix.tox_env }} -- --remote-data=any
    - name: Run slow tests with remote data
      if: "matrix.use_remote_data && matrix.slow"
      run: |
        pip install pytest-remotedata
        tox -e ${{ matrix.tox_env }} -- --remote-data=any -m slow --run-slow
    - name: Upload coverage to codecov
      if: "endsWith(matrix.tox_env, '-cov')"
      uses: codecov/codecov-action@v3
      env:
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
      with:
        file: ./coverage.xml


name: Build & publish documentation to GitHub Pages

on:
  push:
    branches:
      - main
  release:

jobs:
  build-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: true
          fetch-depth: 0
      - uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      - name: Install system dependencies
        run: |
          sudo apt-get update
          sudo apt-get install pandoc
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[docs]"
      - name: Build documentation
        run: sphinx-build -b html -d _build/doctrees . _build/html
        working-directory: docs
      - name: Deploy to GitHub Pages
        if: success()
        uses: crazy-max/ghaction-github-pages@v3
        with:
          target_branch: gh-pages
          build_dir: docs/_build/html
          fqdn: docs.stingray.science
          jekyll: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


ClusterFuzzLite PR fuzzing

name: ClusterFuzzLite PR fuzzing
on:
  workflow_dispatch:
  pull_request:
    branches: [ main ]
    paths: [ src/** ]

permissions: read-all
jobs:
  PR:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        sanitizer: [address]
    steps:
    - name: Build Fuzzers (${{ matrix.sanitizer }})
      id: build
      uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1
      with:
        sanitizer: ${{ matrix.sanitizer }}
        language: python
        bad-build-check: false
        ##storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/ossf/fuzz-introspector.git
        #storage-repo-branch-coverage: gh-pages  # Optional. Defaults to "gh-pages".
    - name: Run Fuzzers (${{ matrix.sanitizer }})
      id: run
      uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        fuzz-seconds: 100
        mode: 'code-change'
        report-unreproducible-crashes: false
        sanitizer: ${{ matrix.sanitizer }}
        #storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/ossf/fuzz-introspector.git
        #storage-repo-branch: main
        #storage-repo-branch-coverage: gh-pages


CodeQL

# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
permissions:
  actions: read
  contents: read
  security-events: read
on:
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ main ]
    paths:
      - 'src/**'
      - 'frontends/**'
      - '.github/workflows/codeql.yml'
  schedule:
    - cron: '35 17 * * 5'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'javascript', 'python' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
    - name: Checkout repository
      uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.

        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality

    # ℹ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

    #   If the Autobuild fails above, remove it and uncomment the following three lines.
    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

    # - run: |
    #   echo "Run, Build Application using script"
    #   ./location_of_script_within_repo/buildscript.sh

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251


Dependency Review

name: 'Dependency Review'
on: [pull_request]

permissions:
  contents: read

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout Repository'
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
      - name: 'Dependency Review'
        uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a


Java Format

name: Java Format
permissions: read-all
on:
  pull_request:
    paths:
      - 'frontends/java/**'
      - '.github/workflows/google-java.yml'
      - 'tools/auto-fuzz/benchmark/jvm/**'
jobs:
  formatting:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
      - uses: axel-op/googlejavaformat-action@v3
        with:
          args: "--replace"
          skip-commit: true
          files:
            - 'frontends/java/**'
            - '.github/workflows/google-java.yml'
            - 'tools/auto-fuzz/benchmark/jvm/**'
      - name: Print diffs
        run: git --no-pager diff --exit-code


JUnit-Frontends

name: JUnit-Frontends

on:
  pull_request:
    paths:
      - 'frontends/**'
      - '.github/workflows/jvm-unit-test.yml'
permissions: read-all
jobs:
  build:
    runs-on: ubuntu-latest
    name: JUnit-Frontends
    steps:
    - name: Checkout
      uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v3.1.0
    - name: Set up OpenJDK 11
      uses: actions/setup-java@v4
      with:
        distribution: 'zulu'
        java-version: '11'
    - name: Test Maven Build
      run: cd frontends/java && mvn --batch-mode -DskipTests clean package
    - name: Unit Test
      run: cd frontends/java && mvn --batch-mode test


Lint and format

name: Lint and format

on: pull_request
permissions: read-all
jobs:
  codestyle:
    runs-on: ubuntu-latest
    name: Codestyle
    steps:
      - name: Check out source repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v2.4.0
      - name: Set up Python environment
        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v3.0.0
        with:
          python-version: "3.8"
      - name: flake8 Lint post process
        uses: py-actions/flake8@84ec6726560b6d5bd68f2a5bed83d62b52bb50ba # v2
        with:
          ignore: "E125,W503,W504,W605"
          max-line-length: "100"
          path: "src/"
      - name: flake8 Lint python frontend
        uses: py-actions/flake8@84ec6726560b6d5bd68f2a5bed83d62b52bb50ba # v2
        with:
          ignore: "E125,W503,W504,W605"
          max-line-length: "100"
          path: "frontends/python/"
      - name: Install yapf
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: yapf code formatting
        run: |
          yapf -d -r ./src/fuzz_introspector/
          yapf -d -r ./tools/auto-fuzz
          yapf -d -r ./tools/web-fuzzing-introspection/app/webapp/
          yapf -d ./tools/web-fuzzing-introspection/app/*.py
          yapf -d -r ./tools/web-fuzzing-introspection/app/static/assets/db


Mypy

name: Mypy

on:
  pull_request:
    paths:
      - 'src/**'
      - 'frontends/python/**'
      - 'oss_fuzz_integration/**'
      - '.github/workflows/mypy.yml'
permissions: read-all
jobs:
  build:
    runs-on: ubuntu-latest
    name: Mypy
    steps:
    - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v2.4.0
    - name: Set up Python 3.8
      uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v3.0.0
      with:
        python-version: 3.8
    - name: Install Dependencies
      run: |
        pip install mypy types-PyYAML
    - name: mypy
      run: |
        cd src && mypy --ignore-missing-imports -m main


Scorecards supply-chain security

name: Scorecards supply-chain security
on:
  # Only the default branch is supported.
  branch_protection_rule:
  schedule:
    - cron: '31 23 * * 4'
  pull_request:
    branches: [ main ]

# Declare default permissions as read only.
permissions: read-all

jobs:
  analysis:
    name: Scorecards analysis
    runs-on: ubuntu-latest
    permissions:
      # Needed to upload the results to code-scanning dashboard.
      security-events: write
      id-token: write

    steps:
      - name: "Checkout code"
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v2.4.0
        with:
          persist-credentials: false

      - name: "Run analysis"
        uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v0.0.1
        with:
          results_file: results.sarif
          results_format: sarif
          # Read-only PAT token. To create it,
          # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
          repo_token: ${{ secrets.SCORECARD_TOKEN }}
          # Publish the results to enable scorecard badges. For more details, see
          # https://github.com/ossf/scorecard-action#publishing-results.
          # If you are installing the action on a private repo, set it to `publish_results: false`
          # or comment out the following line.
          publish_results: true

      # Upload the results as artifacts (optional).
      - name: "Upload artifact"
        uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v2.3.1
        with:
          name: SARIF file
          path: results.sarif
          retention-days: 5

      # Upload the results to GitHub's code scanning dashboard.
      - name: "Upload to code-scanning"
        uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v1.0.26
        with:
          sarif_file: results.sarif


Unit tests

name: Unit tests

on:
  pull_request:
    paths:
      - 'frontends/**'
      - 'src/**'
      - 'test/**'
      - '.github/workflows/testing.yml'
permissions: read-all
jobs:
  unittests:
    runs-on: ubuntu-latest
    name: Unit Tests
    steps:
      - name: Check out source repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v2.4.0
      - name: Set up Python environment
        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v3.0.0
        with:
          python-version: "3.8"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Prepare test case files
        run: |
          cd tests/java && ./runTest.sh test3 && ./runTest.sh test4 && ./runTest.sh test11 && ./runTest.sh test12
      - name: Test unittests and generate coverage report
        run: |
          coverage run -m pytest -vv src/test/
      - name: Display and clean up unittests coverage report
        run: |
          coverage report -m
          rm .coverage


webapp-api-test

name: webapp-api-test

on:
  pull_request:
    branches:
      main
    paths:
      - 'tools/web-fuzzing-introspection/**'
      - '.github/helper/**'
      - '.github/workflows/webapp-api-test.yml'
permissions: read-all
jobs:
  build:
    runs-on: ubuntu-latest
    name: webapp-api-test
    steps:
    - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v2.4.0
    - name: Set up Python 3.8
      uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v3.0.0
      with:
        python-version: 3.8
    - name: Install venv
      run: sudo apt-get install python3-venv
    - name: Set up venv
      run: python3 -m venv tools/web-fuzzing-introspection/.venv
    - name: Active venv dependencies
      run: source tools/web-fuzzing-introspection/.venv/bin/activate && pip install -r tools/web-fuzzing-introspection/requirements.txt
    - name: Run test
      run: source tools/web-fuzzing-introspection/.venv/bin/activate && python3 .github/helper/api_test.py


Mypy-Pyright-webapp

name: Mypy-Pyright-webapp

on:
  pull_request:
    branches:
      main
    paths:
      - 'tools/web-fuzzing-introspection/**'
      - '.github/workflows/webapp-mypy.yml'
permissions: read-all
jobs:
  build:
    runs-on: ubuntu-latest
    name: Mypy-Pyright-webapp
    steps:
    - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v2.4.0
    - name: Set up Python 3.8
      uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v3.0.0
      with:
        python-version: 3.8
    - name: Install venv
      run: sudo apt-get install python3-venv
    - name: Set up venv
      run: cd tools/web-fuzzing-introspection && python3 -m venv .venv
    - name: Active venv dependencies
      run: cd tools/web-fuzzing-introspection && source .venv/bin/activate && pip install -r ./requirements.txt
    - name: Install Dependencies
      run: |
        pip install mypy types-PyYAML pyright types-requests
    - name: mypy
      run: |
        cd tools/web-fuzzing-introspection && mypy --ignore-missing-imports --explicit-package-bases .
    - name: pyright
      run: |
        cd tools/web-fuzzing-introspection && source .venv/bin/activate && pyright . --project=./.pyrightconfig.json --venvpath=./.venv


CI

on:
  push:
  pull_request:
  merge_group:

name: CI
jobs:

  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version:
        - '3.8'
        - '3.9'
        - '3.10'
        - '3.11'
        - '3.12'
        - 'pypy-3.8'
        - 'pypy-3.9'
        - 'pypy-3.10'
        allow-failure:
        - false
        include:
        - python-version: '3.13-dev'
          allow-failure: true
    continue-on-error: ${{ matrix.allow-failure }}
    name: 'test (${{ matrix.python-version }})'
    steps:
    - name: Check out source code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
    - name: Set up PDM
      uses: pdm-project/setup-pdm@v4
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys FA8E1301F4D3932C
        sudo add-apt-repository 'deb http://ppa.launchpad.net/sri-csl/formal-methods/ubuntu bionic main'
        sudo apt-get update
        sudo apt-get install yices2
        pip install codecov build
        pdm install --dev
    - name: Cache YoWASP build products
      uses: actions/cache@v4
      with:
        path: ~/.cache/YoWASP
        key: YoWASP-${{ runner.os }}-${{ hashFiles('./.venv/**/*.wasm') }}
        restore-keys: |
          YoWASP-${{ runner.os }}-
    - name: Run tests
      run: |
        pdm run test
    - name: Submit code coverage
      run: |
        codecov

  smoketest: # If we plug this into downstream projects, does magic smoke escape?
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        project:
          - amaranth-lang/amaranth-boards
          - amaranth-lang/amaranth-stdio
          - amaranth-lang/amaranth-soc
    name: 'smoke (${{ matrix.project }})'
    steps:
    - name: Check out Amaranth source code
      uses: actions/checkout@v4
      with:
        path: amaranth
        fetch-depth: 0
    - name: Check out source code
      uses: actions/checkout@v4
      with:
        repository: ${{ matrix.project }}
        path: project
        fetch-depth: 0
    - name: Set up PDM
      uses: pdm-project/setup-pdm@v4
    - name: Install dependencies
      working-directory: project
      run: |
        pdm install --dev
    - name: Use Amaranth HEAD revision
      working-directory: project
      run: |
        pdm add ../amaranth
    - name: Cache YoWASP build products
      uses: actions/cache@v4
      with:
        path: ~/.cache/YoWASP
        key: YoWASP-${{ runner.os }}-${{ hashFiles('./.venv/**/*.wasm') }}
        restore-keys: |
          YoWASP-${{ runner.os }}-
    - name: Run tests
      working-directory: project
      run: |
        pdm run test

  document:
    runs-on: ubuntu-latest
    steps:
      - name: Check out source code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Fetch tags from upstream repository
        run: |
          git fetch --tags https://github.com/amaranth-lang/amaranth.git
      - name: Set up PDM
        uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: |
          pdm install --dev
      - name: Build documentation
        run: |
          pdm run document
      - name: Upload documentation archive
        uses: actions/upload-artifact@v4
        with:
          name: docs
          path: docs/_build
      - name: Extract schemas
        run: |
          pdm run extract-schemas
      - name: Upload schema archive
        uses: actions/upload-artifact@v4
        with:
          name: schema
          path: schema

  check-links:
    runs-on: ubuntu-latest
    steps:
      - name: Check out source code
        uses: actions/checkout@v4
        with:
            fetch-depth: 0
      - name: Set up PDM
        uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: |
          pdm install --dev
      - name: Check links in documentation
        run: |
          pdm run document-linkcheck

  required: # group all required workflows into one to avoid reconfiguring this in Actions settings
    needs:
      - test
      - document
    if: ${{ always() && !contains(needs.*.result, 'cancelled') }}
    runs-on: ubuntu-latest
    steps:
      - run: ${{ contains(needs.*.result, 'failure') && 'false' || 'true' }}

  publish-docs:
    needs: document
    if: ${{ github.repository == 'amaranth-lang/amaranth' }}
    runs-on: ubuntu-latest
    steps:
      - name: Check out source code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Download documentation archive
        uses: actions/download-artifact@v4
        with:
          name: docs
          path: docs/
      - name: Publish development documentation
        if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' }}
        uses: JamesIves/github-pages-deploy-action@releases/v4
        with:
          repository-name: amaranth-lang/amaranth-lang.github.io
          ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
          branch: main
          folder: docs/
          target-folder: docs/amaranth/latest/
      - name: Publish release documentation
        if: ${{ github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && !contains(github.event.ref, 'dev') }}
        uses: JamesIves/github-pages-deploy-action@releases/v4
        with:
          repository-name: amaranth-lang/amaranth-lang.github.io
          ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
          branch: main
          folder: docs/
          target-folder: docs/amaranth/${{ github.ref_name }}/

  publish-docs-dev:
    needs: document
    if: ${{ github.repository != 'amaranth-lang/amaranth' }}
    runs-on: ubuntu-latest
    steps:
      - name: Check out source code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Download documentation archive
        uses: actions/download-artifact@v4
        with:
          name: docs
          path: pages/docs/${{ github.ref_name }}/
      - name: Disable Jekyll
        run: |
          touch pages/.nojekyll
      - name: Publish documentation for a branch
        uses: JamesIves/github-pages-deploy-action@releases/v4
        with:
          folder: pages/
          clean: false

  publish-schemas:
    needs: [required, publish-docs] # avoid race condition with publish-docs
    if: ${{ github.repository == 'amaranth-lang/amaranth' }}
    runs-on: ubuntu-latest
    steps:
      - name: Check out source code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Download schema archive
        uses: actions/download-artifact@v4
        with:
          name: schema
          path: schema/
      - name: Publish schemas
        if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' }}
        uses: JamesIves/github-pages-deploy-action@releases/v4
        with:
          repository-name: amaranth-lang/amaranth-lang.github.io
          ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
          branch: main
          folder: schema/
          target-folder: schema/amaranth/

  publish-package:
    needs: required
    runs-on: ubuntu-latest
    environment: publish
    permissions:
      id-token: write
    steps:
    - name: Check out source code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
    - name: Build package
      run: |
        pip install build
        python -m build
    - name: Upload package artifact
      uses: actions/upload-artifact@v4
      with:
        name: package
        path: dist/
    - name: Verify package metadata
      run: |
        pip install twine
        twine check dist/*
    - name: Publish package to Test PyPI
      if: ${{ github.repository == 'amaranth-lang/amaranth' && github.event_name == 'push' && github.event.ref == 'refs/heads/main' }}
      uses: pypa/gh-action-pypi-publish@release/v1
      with:
        repository-url: https://test.pypi.org/legacy/
    - name: Publish package to PyPI
      if: ${{ github.repository == 'amaranth-lang/amaranth' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && !contains(github.event.ref, 'dev') }}
      uses: pypa/gh-action-pypi-publish@release/v1

  publish-release:
    needs: publish-package
    if: ${{ github.repository == 'amaranth-lang/amaranth' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && !contains(github.event.ref, 'dev') }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
    - name: Determine release metadata
      id: metadata
      env:
        REF_NAME: ${{ github.ref_name }}
      run: |
        echo name=${REF_NAME/v/} >>$GITHUB_OUTPUT
    - name: Create release
      uses: comnoco/create-release-action@v2
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref_name }}
        release_name: ${{ steps.metadata.outputs.name }}


CodeQL

name: "CodeQL"

on:
  push:
    branches: [develop]
  pull_request:
    branches: [develop]
  schedule:
    - cron: "0 0 * * *"

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        # Override automatic language detection by changing the below list
        language: ["python", "typescript"]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      # Install python dependencies
      - if: matrix.language == 'python'
        name: Setup python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - if: matrix.language == 'python'
        name: Install dependencies with pip
        run: pip install -e .[dev-pinned,pinned]

      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          # Override the default behavior so that the action doesn't attempt
          # to auto-install Python dependencies
          setup-python-dependencies: false

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3


Dependency Review

# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
name: "Dependency Review"
on:
  pull_request:
    branches: [develop]

permissions:
  contents: read

jobs:
  check:
    name: Check
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
      - name: Review dependencies
        uses: actions/dependency-review-action@v4


OSSAR

# This workflow integrates a collection of open source static analysis tools
# with GitHub code scanning. For documentation, or to provide feedback, visit
# https://github.com/github/ossar-action
name: "OSSAR"

on:
  push:
    branches: [develop]
  pull_request:
    branches: [develop]

permissions:
  contents: read

jobs:
  scan:
    name: Scan
    runs-on: windows-latest

    permissions:
      contents: read # for actions/checkout to fetch code
      security-events: write # for github/codeql-action/upload-sarif to upload SARIF results

    steps:
      # Checkout your code repository to scan
      - name: Checkout repository
        uses: actions/checkout@v4

        # Run open source static analysis tools
      - name: Run OSSAR
        uses: github/ossar-action@v1
        id: ossar

        # Upload results to the Security tab
      - name: Upload OSSAR results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: ${{ steps.ossar.outputs.sarifFile }}
          category: ossar


Publisher

name: Publisher

# Trigger on new github release, a tag with format vX.Y.Z is expected (used to tag the docker)
on:
  release:
    types: [published]

env:
  OPENDR_VERSION: ${{ github.event.release.tag_name }}

defaults:
  run:
    shell: bash

jobs:
  publish-wheel:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v7
      with:
        submodules: true
    - name: Set up Python 3.8
      uses: actions/setup-python@v4
      with:
        python-version: 3.8
    - name: Install prerequisites
      run: |
        python -m pip install --upgrade pip
        pip install setuptools wheel twine
    - name: Build Wheel
      run: |
        ./bin/build_wheel.sh
    - name: Upload Wheel
      env:
        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
      run : |
        twine upload dist/*
  publish-docker-cpu:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v3
      with:
        submodules: true
    - name: Get branch name
      id: branch-name
      uses: tj-actions/branch-names@v7
    - name: Build Docker Image
      run: docker build --tag opendr-toolkit:cpu_$OPENDR_VERSION --build-arg branch=${{ steps.branch-name.outputs.current_branch }} --file Dockerfile .
    - name: Login to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_PASSWORD }}
    - name: Publish Image
      run: |
        docker tag opendr-toolkit:cpu_$OPENDR_VERSION opendr/opendr-toolkit:cpu_$OPENDR_VERSION
        docker push opendr/opendr-toolkit:cpu_$OPENDR_VERSION
  publish-docker-cuda:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v3
      with:
        submodules: true
    - name: Get branch name
      id: branch-name
      uses: tj-actions/branch-names@v7
    - name: Build Docker Image
      run: docker build --tag opendr-toolkit:cuda_$OPENDR_VERSION --build-arg branch=${{ steps.branch-name.outputs.current_branch }} --file Dockerfile-cuda .
    - name: Login to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_PASSWORD }}
    - name: Publish Image
      run: |
        docker tag opendr-toolkit:cuda_$OPENDR_VERSION opendr/opendr-toolkit:cuda_$OPENDR_VERSION
        docker push opendr/opendr-toolkit:cuda_$OPENDR_VERSION


Test Packages

name: Test Packages
# This workflow tests the latest (third-party hosted) available builds

on:
  pull_request:
    types: [opened, reopened, synchronize, labeled, unlabeled]
  schedule:
    - cron:  '0 23 * * *'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

defaults:
  run:
    shell: bash

jobs:
  test-wheel:
    if: ${{ contains(github.event.pull_request.labels.*.name, 'test packages') || github.event_name == 'schedule' }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-20.04]
        package:
          - engine
          - utils
          - perception/activity_recognition
          - perception/compressive_learning
          - perception/face_recognition
          - perception/heart_anomaly_detection
          - perception/multimodal_human_centric
          - perception/pose_estimation
          - perception/fall_detection
          - perception/gesture_recognition
          - perception/speech_recognition
          - perception/skeleton_based_action_recognition/costgcn
          - perception/skeleton_based_action_recognition/pstgcn
          - perception/skeleton_based_action_recognition/stbln
          - perception/skeleton_based_action_recognition/stgcn
          - perception/skeleton_based_action_recognition/tagcn
          - perception/semantic_segmentation
          - perception/object_tracking_2d
          # - perception/object_tracking_3d # passes, but disabled due to free() crash
          - perception/object_tracking_3d/single_object_tracking
          - perception/object_detection_2d/centernet
          - perception/object_detection_2d/detr
          - perception/object_detection_2d/gem
          - perception/object_detection_2d/ssd
          - perception/object_detection_2d/nanodet
          - perception/object_detection_2d/yolov3
          - perception/object_detection_2d/yolov5
          - perception/object_detection_2d/retinaface
          - perception/object_detection_2d/nms
          - perception/speech_transcription/vosk
          - perception/speech_transcription/whisper
          # - perception/object_detection_3d # passes, but disabled due to free() crash
          - perception/facial_expression_recognition
          - simulation/human_model_generation
          # - control/mobile_manipulation # not supported in pip?
          #- control/single_demo_grasp
          #- planning/end_to_end_planning
          # - control/multi_object_search # not supported in pip?
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python 3.8
      uses: actions/setup-python@v4
      with:
        python-version: 3.8
    - name: Test Wheel
      run: |
        export DISABLE_BCOLZ_AVX2=true
        sudo apt -y install python3.8-venv libfreetype6-dev git build-essential cmake python3-dev wget libopenblas-dev libsndfile1 libboost-dev libeigen3-dev ffmpeg
        python3 -m venv venv
        source venv/bin/activate
        python3 -m pip install --upgrade pip
        python3 -m pip install wheel==0.38.4
        python3 -m pip install opendr-toolkit-engine
        python3 -m pip install opendr-toolkit
        # Manually install the correct torch version to avoid failures
        python3 -m pip install torch==1.13.1
        export OPENDR_HOME=`pwd`
        # run the test
        python3 -m unittest discover -s tests/sources/tools/${{ matrix.package }}
  test-docker:
    if: ${{ contains(github.event.pull_request.labels.*.name, 'test packages') || github.event_name == 'schedule' }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-20.04]
        package:
          - engine
          - utils
          - perception/activity_recognition
          - perception/compressive_learning
          - perception/face_recognition
          # - perception/heart_anomaly_detection # docker only issues, not affecting functionality
          - perception/multimodal_human_centric
          - perception/pose_estimation
          - perception/fall_detection
          - perception/gesture_recognition
          - perception/speech_recognition
          - perception/skeleton_based_action_recognition/costgcn
          - perception/skeleton_based_action_recognition/pstgcn
          - perception/skeleton_based_action_recognition/stbln
          - perception/skeleton_based_action_recognition/stgcn
          - perception/skeleton_based_action_recognition/tagcn
          - perception/semantic_segmentation
          - perception/object_tracking_2d
          # - perception/object_tracking_3d # passes, but disabled due to free() crash
          - perception/object_detection_2d/centernet
          - perception/object_detection_2d/detr
          - perception/object_detection_2d/gem
          - perception/object_detection_2d/ssd
          - perception/object_detection_2d/nanodet
          - perception/object_detection_2d/yolov3
          - perception/object_detection_2d/yolov5
          - perception/object_detection_2d/retinaface
          - perception/object_detection_2d/nms
          - perception/speech_transcription/vosk
          - perception/speech_transcription/whisper
          # - perception/object_detection_3d # passes, but disabled due to free() crash
          - perception/facial_expression_recognition
          - simulation/human_model_generation
          # - control/mobile_manipulation
          # - control/single_demo_grasp  # disabled in the main suite, see #463
          # - planning/end_to_end_planning
          # - control/multi_object_search
    runs-on: ${{ matrix.os }}
    steps:
    - name: Set up Python 3.8
      uses: actions/setup-python@v4
      with:
        python-version: 3.8
    - name: Test Docker
      run: |
        docker run --name toolkit -i opendr/opendr-toolkit:cpu_v3.0.0 bash
        docker start toolkit
        docker exec -i toolkit bash -c "source bin/activate.sh && source tests/sources/tools/control/mobile_manipulation/run_ros.sh && python3 -m unittest discover -s tests/sources/tools/${{ matrix.package }}"