Unit Test - All Interfaces

name: Unit Test - All Interfaces
on:
  workflow_call:
    secrets:
      codecov_token:
        description: The codecov token to use when uploading coverage files
        required: true
    inputs:
      branch:
        description: The PennyLane branch to checkout and run unit tests for
        required: true
        type: string
      pipeline_mode:
          description: The pipeline mode can be unit-tests, benchmarks, or reference-benchmarks
          required: false
          type: string
          default: 'unit-tests'
      pytest_coverage_flags:
        description: PyTest Coverage flags to pass to all jobs
        required: false
        type: string
        default: --cov=pennylane --cov-append --cov-report=term-missing --cov-report=xml --no-flaky-report --tb=native
      run_lightened_ci:
        description: |
          Indicate if a lightened version of the CI should be run instead of the entire suite.

          The lightened version of the CI includes the following changes:
          - Only Python 3.9 is tested against, instead of 3.9, 3.10, 3.11, 3.12
        required: false
        type: boolean
        default: false
      skip_ci_test_jobs:
        description: |
          Names of jobs (comma separated) that should be skipped on a lightened CI run.
          The value of this variable is only used IF 'run_lightened_ci' is `true`.
          For a full test-suite run, all jobs are triggered.
        required: false
        type: string
        default: ''
      disable_new_opmath:
        description: Whether to disable the new op_math or not when running the tests
        required: false
        type: string
        default: "False"

jobs:
  setup-ci-load:
    runs-on: ubuntu-latest

    steps:
      - name: Setup Python Versions
        id: python_versions

        # All jobs will use the 'default' python versions listed in the dictionary below.
        # Unless the job name exists as a key as well, in which case the python versions listed for the job itself will be used instead.
        run: |
          if [ "${{ inputs.run_lightened_ci }}" == "true" ];
          then
            cat >python_versions.json <<-EOF
          {
            "default": ["3.9"]
          }
          EOF
          else
            cat >python_versions.json <<-EOF
          {
            "default": ["3.9", "3.10", "3.11", "3.12"],
            "torch-tests": ["3.9", "3.11"],
            "tf-tests": ["3.9", "3.11"],
            "jax-tests": ["3.9", "3.12"],
            "all-interfaces-tests": ["3.9"],
            "external-libraries-tests": ["3.9"],
            "qcut-tests": ["3.9"],
            "qchem-tests": ["3.9"],
            "gradients-tests": ["3.9"],
            "data-tests": ["3.9", "3.10"],
            "device-tests": ["3.9"]
          }
          EOF
          fi

          jq . python_versions.json
          echo "python_versions=$(jq -r tostring python_versions.json)" >> $GITHUB_OUTPUT

      - name: Setup Matrix Max Parallel
        id: max_parallel
        run: |
          if [ "${{ inputs.run_lightened_ci }}" == "true" ];
          then
            cat >matrix_max_parallel.json <<-EOF
          {
            "default": 1,
            "core-tests": 5,
            "gradients-tests": 2,
            "jax-tests": 5,
            "tf-tests": 3
          }
          EOF
          else
            cat >matrix_max_parallel.json <<-EOF
          {
            "default": 1,
            "core-tests": 5,
            "jax-tests": 10,
            "tf-tests": 6,
            "torch-tests": 2
          }
          EOF
          fi

          jq . matrix_max_parallel.json
          echo "matrix_max_parallel=$(jq -r tostring matrix_max_parallel.json)" >> $GITHUB_OUTPUT

      - name: Setup Job to Skip
        id: jobs_to_skip
        env:
          JOBS_TO_SKIP: ${{ inputs.skip_ci_test_jobs }}
        run: |
          if [ "${{ inputs.run_lightened_ci }}" == "true" ];
          then
            skipped_jobs=$(echo -n "$JOBS_TO_SKIP" | python -c 'import json, sys; print(json.dumps(list(map(lambda job: job.strip(), filter(None, sys.stdin.read().split(","))))))')
            echo "The following jobs will be skipped: $skipped_jobs"
            echo "jobs_to_skip=$skipped_jobs" >> $GITHUB_OUTPUT
          else
            echo 'jobs_to_skip=[]' >> $GITHUB_OUTPUT
          fi

    outputs:
      matrix-max-parallel: ${{ steps.max_parallel.outputs.matrix_max_parallel }}
      python-version: ${{ steps.python_versions.outputs.python_versions }}
      jobs-to-skip: ${{ steps.jobs_to_skip.outputs.jobs_to_skip }}

  torch-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).torch-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).torch-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'torch-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: torch-tests (${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: core-interfaces-coverage-torch-${{ matrix.python-version }}
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: false
      install_tensorflow: false
      install_pytorch: true
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: torch and not qcut and not finite-diff and not param-shift
      requirements_file: ${{ strategy.job-index == 0 && 'torch.txt' || '' }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  autograd-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).autograd-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).autograd-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'autograd-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: autograd-tests (${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: core-interfaces-coverage-autograd-${{ matrix.python-version }}
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: false
      install_tensorflow: false
      install_pytorch: false
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: autograd and not qcut and not finite-diff and not param-shift
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  tf-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).tf-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        group: [1, 2, 3]
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).tf-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'tf-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: tf-tests (${{ matrix.group }}, ${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: core-interfaces-coverage-tf-${{ matrix.python-version }}-${{ matrix.group }}
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: false
      install_tensorflow: true
      install_pytorch: false
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: tf and not qcut and not finite-diff and not param-shift
      pytest_additional_args: --splits 3 --group ${{ matrix.group }} --durations-path='.github/workflows/tf_tests_durations.json'
      additional_pip_packages: pytest-split
      requirements_file: ${{ strategy.job-index == 0 && 'tf.txt' || '' }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  jax-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).jax-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        group: [1, 2, 3, 4, 5]
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).jax-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'jax-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: jax-tests (${{ matrix.group }}, ${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: core-interfaces-coverage-jax-${{ matrix.python-version }}-${{ matrix.group }}
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: true
      install_tensorflow: false
      install_pytorch: false
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: jax and not qcut and not finite-diff and not param-shift
      pytest_additional_args: --splits 5 --group ${{ matrix.group }} --durations-path='.github/workflows/jax_tests_durations.json'
      additional_pip_packages: pytest-split
      requirements_file: ${{ strategy.job-index == 0 && 'jax.txt' || '' }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  core-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).core-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        group: [1, 2, 3, 4, 5]
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).core-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'core-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: core-tests (${{ matrix.group }}, ${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: core-interfaces-coverage-core-${{ matrix.python-version }}-${{ matrix.group }}
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: false
      install_tensorflow: false
      install_pytorch: false
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: core and not qcut and not finite-diff and not param-shift
      pytest_additional_args: --splits 5 --group ${{ matrix.group }} --durations-path='.github/workflows/core_tests_durations.json'
      additional_pip_packages: pytest-split
      requirements_file: ${{ strategy.job-index == 0 && 'core.txt' || '' }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  all-interfaces-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).all-interfaces-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).all-interfaces-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'all-interfaces-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: all-interfaces-tests (${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: all-interfaces-coverage
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: true
      install_tensorflow: true
      install_pytorch: true
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: all_interfaces
      requirements_file: ${{ strategy.job-index == 0 && 'all_interfaces.txt' || '' }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  external-libraries-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).external-libraries-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).external-libraries-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'external-libraries-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: external-libraries-tests (${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: external-libraries-tests-coverage
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: true
      install_tensorflow: true
      install_pytorch: false
      # using lightning master does not work for the tests with external libraries
      install_pennylane_lightning_master: false
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: external
      additional_pip_packages: pyzx pennylane-catalyst matplotlib stim quimb
      requirements_file: ${{ strategy.job-index == 0 && 'external.txt' || '' }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  qcut-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).qcut-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).qcut-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'qcut-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: qcut-tests (${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: qcut-coverage
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: true
      install_tensorflow: true
      install_pytorch: true
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: qcut
      additional_pip_packages: kahypar==1.1.7 opt_einsum
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  qchem-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).qchem-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).qchem-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'qchem-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: qchem-tests (${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: qchem-coverage
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: false
      install_tensorflow: false
      install_pytorch: false
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: qchem
      additional_pip_packages: openfermionpyscf basis-set-exchange
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  gradients-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).gradients-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        config:
          - suite: finite-diff
          - suite: param-shift
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).gradients-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'gradients-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: gradients-tests (${{ matrix.config.suite }}, ${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: gradients-${{ matrix.config.suite }}-coverage
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: true
      install_tensorflow: true
      install_pytorch: true
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: ${{ matrix.config.suite }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  data-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).data-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).data-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'data-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: data-tests (${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: data-coverage
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: false
      install_tensorflow: false
      install_pytorch: false
      install_pennylane_lightning_master: true
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_markers: data
      additional_pip_packages: h5py
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  device-tests:
    needs:
      - setup-ci-load
    strategy:
      max-parallel: >-
        ${{
           fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).device-tests
           || fromJSON(needs.setup-ci-load.outputs.matrix-max-parallel).default
         }}
      matrix:
        config:
          - device: default.qubit.legacy
            shots: None
          - device: default.qubit
            shots: None
          - device: default.qubit
            shots: 10000
          - device: default.qubit.legacy
            shots: 10000
          # - device: default.qubit.tf
          #   shots: None
          - device: default.qubit.autograd
            shots: None
            skip_interface: jax,tf,torch
          - device: default.mixed
            shots: None
        python-version: >-
          ${{
            fromJSON(needs.setup-ci-load.outputs.python-version).device-tests
            || fromJSON(needs.setup-ci-load.outputs.python-version).default
           }}
    if: ${{ !contains(fromJSON(needs.setup-ci-load.outputs.jobs-to-skip), 'device-tests') }}
    uses: ./.github/workflows/unit-test.yml
    with:
      job_name: device-tests (${{ matrix.config.device }}, ${{ matrix.config.shots }}, ${{ matrix.python-version }})
      branch: ${{ inputs.branch }}
      coverage_artifact_name: devices-coverage-${{ matrix.config.device }}-${{ matrix.config.shots }}
      python_version: ${{ matrix.python-version }}
      pipeline_mode: ${{ inputs.pipeline_mode }}
      install_jax: ${{ !contains(matrix.config.skip_interface, 'jax') }}
      install_tensorflow: ${{ !contains(matrix.config.skip_interface, 'tf') }}
      install_pytorch: ${{ !contains(matrix.config.skip_interface, 'torch') }}
      install_pennylane_lightning_master: true
      pytest_test_directory: pennylane/devices/tests
      pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
      pytest_additional_args: --device=${{ matrix.config.device }} --shots=${{ matrix.config.shots }}
      disable_new_opmath: ${{ inputs.disable_new_opmath }}

  upload-to-codecov:
    runs-on: ubuntu-latest

    needs:
      - torch-tests
      - autograd-tests
      - tf-tests
      - jax-tests
      - core-tests
      - all-interfaces-tests
      - external-libraries-tests
      - qcut-tests
      - qchem-tests
      - gradients-tests
      - data-tests
      - device-tests

    # Run this even if any of the above jobs are skipped but not if any of the jobs failed
    if: >-
      ${{
        always() &&
        !contains(needs.*.result, 'failure') &&
        !contains(needs.*.result, 'cancelled') &&
        inputs.pipeline_mode == 'unit-tests'
       }}

    steps:
      # Checkout repo so Codecov action is able to resolve git HEAD reference
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ inputs.branch }}

      - name: Down Coverage Artifacts
        uses: actions/download-artifact@v3

      - name: Upload to Codecov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.codecov_token }}
          fail_ci_if_error: true  # upload errors should be caught early


Legacy opmath tests

name: Legacy opmath tests

on:
  schedule:
    - cron: "0 2 * * *"
  workflow_dispatch:

jobs:
  tests:
    uses: ./.github/workflows/interface-unit-tests.yml
    secrets:
      codecov_token: ${{ secrets.CODECOV_TOKEN }}
    with:
      branch: 'master'
      run_lightened_ci: false
      disable_new_opmath: "True"


Sync release candidate with master

# This workflow will sync changes in the release candidate branch to master during a
# feature freeze. The action is run daily and will only create a pull request when
# there exists a release candidate branch corresponding to the current package version
# as declared in pennylane/_version.py, that is if the branch "v0.23.0-rc0" exists while
# the package version is "v0.24.0-dev".

name: Sync release candidate with master

# Controls when the workflow will run
on:
  # Scheduled trigger every weekday at 2:47am UTC
  schedule:
  - cron:  '47 2 * * 1-5'

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

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job to sync the master branch with changes from the rc
  sync:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out the PennyLane repository
      - uses: actions/checkout@v3

      # Check for the existence of an rc branch for the current PennyLane version
      - name: Check for rc branch
        run: |
          VERSION=$(python setup.py --version)
          IFS=. read MAJ MIN PAT <<< "${VERSION%.dev0}"
          RC_BRANCH="v${MAJ}.$((MIN-1)).${PAT}-rc0"
          if git ls-remote --exit-code origin "refs/heads/$RC_BRANCH"; then
            echo "branch_exists=true" >> $GITHUB_ENV
            echo "rc_branch=$RC_BRANCH" >> $GITHUB_ENV
          else
            echo "branch_exists=false" >> $GITHUB_ENV
          fi
          echo "new_changes=false" >> $GITHUB_ENV

      # Create a new branch at from the current rc to be used in the PR
      - name: Create temp branch
        if: ${{ env.branch_exists == 'true' }}
        run: |
          git fetch
          git checkout "${{ env.rc_branch }}"
          BRANCH="rc_$(date +'%Y-%m-%d-%H-%M-%S')"
          echo "tmp_branch=$BRANCH" >> $GITHUB_ENV
          git checkout -b "$BRANCH"
          # Exclude some files known to be divergent during the release process from the PR
          git checkout master -- pennylane/_version.py
          git checkout master -- doc/development/release_notes.md
          git config user.name "GitHub Actions Bot"
          git config user.email "<>"
          git commit -m "exclude files from pr"
          # check for new changes on the rc branch only, based on the diff between master and rc
          if ! git diff master HEAD --quiet -- $(git diff --name-only master...HEAD); then
            echo "new_changes=true" >> $GITHUB_ENV
            git push --set-upstream origin "$BRANCH"
          fi

      # Create PR to master
      - name: Create pull request
        if: ${{ env.new_changes == 'true' }}
        uses: repo-sync/pull-request@v2
        with:
          source_branch: "${{ env.tmp_branch }}"
          destination_branch: "master"
          github_token: "${{ secrets.GITHUB_TOKEN }}"
          pr_title: "Daily rc sync to master"
          pr_body: "Automatic sync from the release candidate to master during a feature freeze."
          pr_allow_empty: false
          pr_draft: false
          pr_reviewer: "astralcai,albi3ro"


GPU Tests

name: GPU Tests
on:
  push:
    branches:
      - master
  pull_request:
    types:
      - opened
      - reopened
      - labeled
      - synchronize

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

env:
  TORCH_VERSION: 2.2.0

jobs:
  gpu-tests:
    runs-on:
      - self-hosted
      - ubuntu-22.04
      - gpu

    if: >-
      ${{
        github.event_name == 'push' ||
        (
          github.event_name == 'pull_request' &&
          contains(github.event.pull_request.labels.*.name, 'gpu') &&
          github.event.pull_request.state == 'open'
        )
      }}

    strategy:
      max-parallel: 2
      matrix:
        python-version:
          - 3.9
          - '3.10'
          - '3.11'

    env:
      SUITE: gpu

    steps:
      - name: Update PATH and LD_LIBRARY_PATH
        run: |
          echo "$HOME/.local/bin" >> $GITHUB_PATH
          echo "/usr/local/cuda/bin" >> $GITHUB_PATH
          echo "LD_LIBRARY_PATH=/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" >> $GITHUB_ENV

      - name: Checkout Repo
        uses: actions/checkout@v3
        with:
          fetch-depth: 1

      - name: Setup Python
        id: setup_python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      # Since the self-hosted runner can be re-used. It is best to set up all package
      # installations in a virtual environment that gets cleaned at the end of each workflow run
      - name: Setup Python virtual environment
        id: setup_venv
        env:
          VENV_NAME: ${{ github.workspace }}/venv_${{ steps.setup_python.outputs.python-version }}_${{ github.sha }}
        run: |
          # Clear any pre-existing venvs
          rm -rf venv_*

          # Create new venv for this workflow_run
          python --version
          python -m venv ${{ env.VENV_NAME }}

          # Add the venv to PATH for subsequent steps
          echo ${{ env.VENV_NAME }}/bin >> $GITHUB_PATH

          # Adding venv name as an output for subsequent steps to reference if needed
          echo "venv_name=${{ env.VENV_NAME }}" >> $GITHUB_OUTPUT

      - name: Display Python-Path
        run: |
          which python
          which pip

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip && pip install wheel --upgrade
          pip install -r requirements-ci.txt --upgrade
          pip install -r requirements-dev.txt --upgrade

      - name: Install Torch
        run: |
          python -m pip install --no-cache-dir torch==${{ env.TORCH_VERSION }} -f https://download.pytorch.org/whl/cu118/torch_stable.html

      - name: Install PennyLane
        run: |
          python setup.py bdist_wheel
          pip install dist/PennyLane*.whl

      - name: Install Lightning-master
        run: python -m pip install -i https://test.pypi.org/simple/ PennyLane-Lightning --pre --upgrade

      - name: Run tests
        run: python -m pytest -m "${{ env.SUITE }}" tests

      # Cleans up the working directory so subsequent jobs on same runner get a clean working directory
      # This step runs even if any of the previous steps fail
      - name: Cleanup
        if: always()
        run: |
          rm -rf ${{ steps.setup_venv.outputs.venv_name }}
          rm -rf *
          rm -rf .git
          rm -rf .gitignore
          rm -rf .github
          pip cache purge


Tests

name: Tests
on:
  push:
    branches:
      - master
  pull_request:

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

env:
  DEPS_BRANCH: bot/stable-deps-update

jobs:
  tests:
    uses: ./.github/workflows/interface-unit-tests.yml
    secrets:
      codecov_token: ${{ secrets.CODECOV_TOKEN }}
    with:
      branch: ${{ github.ref }}

      # Run a 'lightened' version of the CI on Pull Requests by default
      # Unless the label `ci:run-full-test-suite` is attached to the PR.
      # Always runs the full suite for push events.
      run_lightened_ci: >-
        ${{
          github.event_name == 'pull_request'
          && !contains(github.event.pull_request.labels.*.name, 'ci:run-full-test-suite')
          || false
         }}

  upload-stable-deps:
    needs: tests
    runs-on: ubuntu-latest
    if: ${{ github.event_name == 'push' }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
          ref: master

      - name: Prepare local repo
        run: |
          git config user.name "GitHub Actions Bot"
          git config user.email "<>"
          if git ls-remote --exit-code origin "refs/heads/${{ env.DEPS_BRANCH }}"; then
            git checkout "${{ env.DEPS_BRANCH }}"
          else
            git checkout -b "${{ env.DEPS_BRANCH }}"
          fi

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: frozen-*
          path: .github/stable/
          merge-multiple: true

      - name: Determine if changes have been made
        id: changed
        run: |
          echo "has_changes=$(git status --porcelain | wc -l | awk '{print $1}')" >> $GITHUB_OUTPUT

      - name: Stage changes
        if: steps.changed.outputs.has_changes != '0'
        run: |
          git add .github/stable/
          git commit -m "Update changed dependencies"
          git push -f --set-upstream origin "${{ env.DEPS_BRANCH }}"

      # Create PR to master
      - name: Create pull request
        if: steps.changed.outputs.has_changes != '0'
        uses: repo-sync/pull-request@v2
        with:
          source_branch: "${{ env.DEPS_BRANCH }}"
          destination_branch: "master"
          github_token: "${{ secrets.GITHUB_TOKEN }}"
          pr_title: "Update stable dependency files"
          # if updating the pr_body, please also update the same text in the docs.yml file
          pr_body: |
            Automatic update of stable requirement files to snapshot valid python environments.

            Because bots are not able to trigger CI on their own, please do so by pushing an empty commit to this branch using the following command:

            ```
            git commit --allow-empty -m 'trigger ci'
            ```

            Alternatively, wait for this branch to be out-of-date with master, then just use the "Update branch" button!

            Note that it is expected that the PennyLane-Lightning repo is a version ahead of the release, because the
            version number is taken from the dev branch. Trying to `pip install` from the files will fail until that
            major version of Lightning is released.

            If pip install fails with a not found error when installing because of this, it can be fixed by manually
            downgrading the PennyLane-Lightning version number in the file by 1 version and trying again.
          pr_allow_empty: false
          pr_draft: false


Unit Test

name: Unit Test
on:
  workflow_call:
    inputs:
      job_name:
        description: The name of the Job as it would appear on GitHub Actions UI
        required: true
        type: string
      branch:
        description: The PennyLane branch to checkout and run unit tests for
        required: true
        type: string
      coverage_artifact_name:
        description: Name of the artifact file that will contain the coverage file for codevoc
        required: true
        type: string
      checkout_fetch_depth:
        description: How many commits to checkout from HEAD of branch passed
        required: false
        type: number
        default: 1
      python_version:
        description: The version of Python to use in order to run unit tests
        required: false
        type: string
        default: '3.9'
      pipeline_mode:
        description: The pipeline mode can be unit-tests, benchmarks, or reference-benchmark
        required: false
        type: string
        default: 'unit-tests'
      install_jax:
        description: Indicate if JAX should be installed or not
        required: false
        type: boolean
        default: true
      jax_version:
        description: The version of JAX to install. Leave empty to install latest version.
        required: false
        type: string
        default: ''
      install_tensorflow:
        description: Indicate if TensorFlow should be installed or not
        required: false
        type: boolean
        default: true
      tensorflow_version:
        description: The version of TensorFlow to install. Leave empty to install latest version.
        required: false
        type: string
        default: ''
      install_pytorch:
        description: Indicate if PyTorch should be installed or not
        required: false
        type: boolean
        default: true
      pytorch_version:
        description: The version of PyTorch to install. Leave empty to install latest version.
        required: false
        type: string
        default: ''
      install_pennylane_lightning_master:
        description: Indicate if PennyLane-Lightning should be installed from the master branch
        required: false
        type: boolean
        default: true
      pytest_test_directory:
        description: The directory where the PennyLane tests are that should be run by PyTest
        required: false
        type: string
        default: tests
      pytest_coverage_flags:
        description: Coverage flags for PyTest
        required: false
        type: string
        default: ''
      pytest_markers:
        description: Custom mark string to pass to PyTest
        required: false
        type: string
        default: ''
      pytest_additional_args:
        description: Additional arguments to pass to PyTest
        required: false
        type: string
        default: ''
      additional_pip_packages:
        description: Additional packages to install. Values will be passed to pip install {value}
        required: false
        type: string
        default: ''
      requirements_file:
        description: File name to store stable version of requirements for a test group
        required: false
        type: string
        default: ''
      disable_new_opmath:
        description: Whether to disable the new op_math or not when running the tests
        required: false
        type: string
        default: "False"

jobs:
  test:
    name: ${{ inputs.job_name }}
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ inputs.branch }}
          fetch-depth: ${{ inputs.checkout_fetch_depth }}
          repository: PennyLaneAI/pennylane

      - name: Determine benchmark name
        if: ${{ inputs.pipeline_mode != 'unit-tests' }}
        id: benchmark_name
        run: |
          job_name="${{ inputs.job_name }}"
          _benchmark_name=${job_name//[(,)]/""}
          _benchmark_name=${_benchmark_name//[(" ")]/"-"}
          echo "benchmark_name=$_benchmark_name" >> $GITHUB_OUTPUT

      - name: Cache reference benchmarks
        if: ${{ inputs.pipeline_mode != 'unit-tests' }}
        id: benchmark-cache
        uses: actions/cache@v3
        with:
          path: benchmark_reference
          key: ${{ steps.benchmark_name.outputs.benchmark_name }}-benchmarks_references

      - name: Check if the shared part of the job will continue
        id: continue
        run: >-
          echo "confirm=${{
            contains(fromJSON('["unit-tests", "benchmarks"]'), inputs.pipeline_mode)
            || (inputs.pipeline_mode == 'reference-benchmarks'
            && steps.benchmark-cache.outputs.cache-hit != 'true' )}}" >> $GITHUB_OUTPUT

      - name: Install PennyLane and dependencies
        if: steps.continue.outputs.confirm == 'true'
        uses: ./.github/workflows/install_deps
        with:
          python_version: ${{ inputs.python_version }}
          install_pytorch: ${{ inputs.install_pytorch }}
          install_tensorflow: ${{ inputs.install_tensorflow }}
          install_jax: ${{ inputs.install_jax }}
          additional_pip_packages: ${{ inputs.additional_pip_packages }}
          install_pennylane_lightning_master: ${{ inputs.install_pennylane_lightning_master }}
          requirements_file: ${{ inputs.requirements_file }}

      - name: Set PyTest Args
        if: steps.continue.outputs.confirm == 'true'
        id: pytest_args
        env:
          PIPELINE_MODE: ${{ inputs.pipeline_mode }}
          PYTEST_COVERAGE_ARGS: ${{ inputs.pytest_coverage_flags }}
          PYTEST_PARALLELISE_ARGS: -n auto
          PYTEST_BENCHMARKS_ARGS: --benchmark-enable --benchmark-only --benchmark-json=benchmarks.json
          PYTEST_ADDITIONAL_ARGS: ${{ inputs.pytest_additional_args }}
        run: |
          if [[ "$PIPELINE_MODE" =~ .*"benchmarks".* ]]; then
            echo "args=$PYTEST_BENCHMARKS_ARGS $PYTEST_ADDITIONAL_ARGS" >> $GITHUB_OUTPUT
          else
            echo "args=$PYTEST_COVERAGE_ARGS $PYTEST_PARALLELISE_ARGS $PYTEST_ADDITIONAL_ARGS" >> $GITHUB_OUTPUT
          fi

      - name: Run PennyLane Unit Tests
        if: steps.continue.outputs.confirm == 'true'
        env:
          PYTEST_MARKER: ${{ inputs.pytest_markers != '' && format('-m "{0}"', inputs.pytest_markers) || '' }}
          COV_CORE_SOURCE: pennylane
          COV_CORE_CONFIG: .coveragerc
          COV_CORE_DATAFILE: .coverage.eager
          TF_USE_LEGACY_KERAS: "1"  # sets to use tf-keras (Keras2) instead of keras (Keras3) when running TF tests
        # Calling PyTest by invoking Python first as that adds the current directory to sys.path
        run: python -m pytest ${{ inputs.pytest_test_directory }} ${{ steps.pytest_args.outputs.args }} ${{ env.PYTEST_MARKER }} --disable-opmath=${{ inputs.disable_new_opmath }}

      - name: Adjust coverage file for Codecov
        if: inputs.pipeline_mode == 'unit-tests'
        run: bash <(sed -i 's/filename=\"/filename=\"pennylane\//g' coverage.xml)

      - name: Upload Coverage File
        if: inputs.pipeline_mode == 'unit-tests'
        uses: actions/upload-artifact@v3
        with:
          name: ${{ inputs.coverage_artifact_name }}
          path: coverage.xml

      # If this is a reference benchmark move file to the cached directory, and we have a non-empty reference benchmark file.
      - name: Move reference pytest benchmark file to cache
        if: inputs.pipeline_mode == 'reference-benchmarks' && hashFiles('benchmarks.json') != ''
        run: mkdir -p ${{ github.workspace }}/benchmark_reference && cp benchmarks.json "$_"

      - name: Convert pytest benchmark JSON files to XUBM-JSON
        if: inputs.pipeline_mode == 'benchmarks' && hashFiles('benchmarks.json') != '' && hashFiles('benchmark_reference/benchmarks.json') != ''
        run: |
          mkdir ${{ github.workspace }}/benchmark_results
          python .github/workflows/scripts/benchmarks/convert_pytest_JSON_to_XUBM.py --filename_XUBM benchmark_results/benchmarks_xubm.json --author ${{ github.event.pull_request.user.login }} --github_reference ${{ github.ref }}
          python .github/workflows/scripts/benchmarks/convert_pytest_JSON_to_XUBM.py --filename benchmark_reference/benchmarks.json --filename_XUBM benchmark_reference/benchmarks_xubm.json --author ${{ github.event.pull_request.user.login }}

      - name: Plotting benchmark graphs
        if: inputs.pipeline_mode == 'benchmarks' && hashFiles('benchmark_results/benchmarks_xubm.json') != '' && hashFiles('benchmark_reference/benchmarks_xubm.json') != ''
        run: |
          python .github/workflows/scripts/benchmarks/plot_benchmarks.py --graph_name ${{ steps.benchmark_name.outputs.benchmark_name }}

      # Merge benchmarks data in a single JSON. Create a CSV file with runtimes. Move data to the artifact directory.
      - name: Post-process benchmark data
        if: inputs.pipeline_mode == 'benchmarks' && hashFiles('benchmark_results/benchmarks_xubm.json') != '' && hashFiles('benchmark_reference/benchmarks_xubm.json') != ''
        run: |
          python .github/workflows/scripts/benchmarks/export_benchmarks_data.py

      # If this is an assessment benchmark, upload the data as an artifact.
      - name: Upload pytest benchmark results
        if: inputs.pipeline_mode == 'benchmarks'
        uses: actions/upload-artifact@v3
        with:
          name: ${{ steps.benchmark_name.outputs.benchmark_name }}-benchmarks
          path: ${{ github.workspace }}/benchmark_results


Upload

name: Upload
on:
  release:
    types: [published]

jobs:
  tests:
    uses: ./.github/workflows/interface-unit-tests.yml
    secrets:
      codecov_token: ${{ secrets.CODECOV_TOKEN }}
    with:
      branch: ${{ github.ref }}

  upload:
    runs-on: ubuntu-latest
    # Make sure that the PennyLane is not released unless the tests are passing.
    needs:
      - tests
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 1

      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.9

      - name: Build PennyLane wheel
        run: |
          python -m pip install --upgrade pip wheel
          pip install -r requirements-ci.txt --upgrade
          python setup.py bdist_wheel

      - name: Publish
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.PYPI }}


Auto Update README

name: Auto Update README

on:
  schedule:
    - cron: '30 19 * * *'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
      with:
        ref: main
    - name: Setup Python
      uses: actions/setup-python@v1
      with:
          python-version: '3.x'
          architecture: 'x64'
    - name: Setup pip
      run: |
        python -m pip install --upgrade pip
        pip install pytz
    - name: Run assemble.py
      run: |
         python scripts/auto_update.py --all
    - name: Commits
      run: |
        git config --local user.email "tony9402@naver.com"
        git config --local user.name "tony9402"
        git add .
        git commit -m "Auto UPDATE (ALL)"
    - name: Push
      uses: ad-m/github-push-action@master
      with:
         branch: 'main'
         github_token: $


Tests

name: Tests

on: [push]

jobs:
  unittest:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.8, 3.9, '3.10', '3.11']
    steps:
    - name: Checkout PyAutoConf
      uses: actions/checkout@v2
      with:
        repository: rhayes777/PyAutoConf
        path: PyAutoConf
    - name: Checkout PyAutoFit
      uses: actions/checkout@v2
      with:
        path: PyAutoFit
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}
    - uses: actions/cache@v2
      id: cache-pip
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
        restore-keys: |
          ${{ runner.os }}-pip-
    - name: Extract branch name
      shell: bash
      run: |
        cd PyAutoFit
        echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
      id: extract_branch
    - name: Change to same branch if exists in deps
      shell: bash
      run: |
        export PACKAGES=("PyAutoConf")
        export BRANCH="${{ steps.extract_branch.outputs.branch }}"
        for PACKAGE in ${PACKAGES[@]}; do
          pushd $PACKAGE
          export existed_in_remote=$(git ls-remote --heads origin ${BRANCH})
          if [[ -z ${existed_in_remote} ]]; then
            echo "Branch $BRANCH did not exist in $PACKAGE"
          else
            echo "Branch $BRANCH did exist in $PACKAGE"
            git fetch
            git checkout $BRANCH
          fi
          popd
        done
    - name: Install dependencies
 #     if: steps.cache-pip.outputs.cache-hit != 'true'
      run: |
        pip3 install --upgrade pip
        pip3 install setuptools
        pip3 install wheel
        pip3 install pytest coverage pytest-cov
        pip3 install -r PyAutoConf/requirements.txt
        pip3 install -r PyAutoFit/requirements.txt
        pip3 install -r PyAutoFit/optional_requirements.txt
        pip3 install -r PyAutoFit/build_requirements.txt
    - name: Run tests
      run: |
        export ROOT_DIR=`pwd`
        export PYTHONPATH=$PYTHONPATH:$ROOT_DIR/PyAutoConf
        export PYTHONPATH=$PYTHONPATH:$ROOT_DIR/PyAutoFit
        pushd PyAutoFit
        pytest --cov autofit --cov-report xml:coverage.xml
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
    - name: Slack send
      if: ${{ failure() }}
      id: slack
      uses: slackapi/slack-github-action@v1.21.0
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      with:
        channel-id: C03S98FEDK2
        payload: |
                {
                  "text": "${{ github.repository }}/${{ github.ref_name }} (Python ${{ matrix.python-version }}) build result: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
                }


Upload Python Package

# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

name: Upload Python Package

on:
  release:
    types: [created]

jobs:
  deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install setuptools wheel twine
    - name: Build and publish
      env:
        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
      run: |
        python setup.py sdist bdist_wheel
        twine upload dist/*


Close stale issues and PRs

name: 'Close stale issues and PRs'
on:
  schedule:
    - cron: '30 1 * * *'

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


auto-merge

name: auto-merge

on:
  pull_request:

jobs:
  auto-merge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ahmadnassri/action-dependabot-auto-merge@v2
        with:
          target: minor
          github-token: ${{ secrets.mytoken }}


Build and Deploy Clubs

# ========================================
# Note:  If you make changes to this CI/CD, please include someone from DevOps in the list of reviewers for the PR.
# ========================================
name: Build and Deploy Clubs

on: push

jobs:
  backend-check:
    name: "Backend Check"
    uses: pennlabs/shared-actions/.github/workflows/django.yaml@507f4cf1cc88259fc8625fb3a35bd3f18fc91cd5
    secrets: inherit
    with:
      projectName: pennclubs
      path: backend
      pythonVersion: 3.11-bookworm
      flake: false
      black: false
      ruff: true

  frontend-check:
    name: "Frontend Check"
    uses: pennlabs/shared-actions/.github/workflows/react-check.yaml@v0.1
    with:
      path: frontend
      nodeVersion: 20.11.1

  build-backend:
    name: Build backend
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: docker/setup-qemu-action@v1
      - uses: docker/setup-buildx-action@v1
      - name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: buildx-build-backend
      - name: Build/Publish
        uses: docker/build-push-action@v2
        with:
          context: backend
          file: backend/Dockerfile
          push: false
          cache-from: type=local,src=/tmp/.buildx-cache,type=registry,ref=pennlabs/penn-clubs-backend:latest
          cache-to: type=local,dest=/tmp/.buildx-cache
          tags: pennlabs/penn-clubs-backend:latest,pennlabs/penn-clubs-backend:${{ github.sha }}
          outputs: type=docker,dest=/tmp/image.tar
      - uses: actions/upload-artifact@v2
        with:
          name: build-backend
          path: /tmp/image.tar
    needs: backend-check

  build-frontend:
    name: Build frontend
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: docker/setup-qemu-action@v1
      - uses: docker/setup-buildx-action@v1
      - name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: buildx-build-frontend
      - name: Build/Publish
        uses: docker/build-push-action@v2
        with:
          context: frontend
          file: frontend/Dockerfile
          push: false
          cache-from: type=local,src=/tmp/.buildx-cache,type=registry,ref=pennlabs/penn-clubs-frontend:latest
          cache-to: type=local,dest=/tmp/.buildx-cache
          tags: pennlabs/penn-clubs-frontend:latest,pennlabs/penn-clubs-frontend:${{ github.sha }}
          outputs: type=docker,dest=/tmp/image.tar
      - uses: actions/upload-artifact@v2
        with:
          name: build-frontend
          path: /tmp/image.tar
    needs: frontend-check

  publish:
    name: Publish Images
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/master'
    steps:
      - uses: actions/checkout@v2
      - uses: actions/download-artifact@v2
      - uses: geekyeggo/delete-artifact@v1
        with:
          name: |-
            build-backend
            build-frontend
      - name: Load docker images
        run: |-
          docker load --input build-backend/image.tar
          docker load --input build-frontend/image.tar
      - uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - name: Push docker images
        run: |-
          docker push -a pennlabs/penn-clubs-backend
          docker push -a pennlabs/penn-clubs-frontend
    needs:
      - build-backend
      - build-frontend

  deploy:
    name: "Deploy"
    uses: pennlabs/shared-actions/.github/workflows/deployment.yaml@v0.1

    with:
      githubRef: ${{ github.ref }}
      gitSha: ${{ github.sha }}

    secrets:
      AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
      GH_AWS_ACCESS_KEY_ID: ${{ secrets.GH_AWS_ACCESS_KEY_ID }}
      GH_AWS_SECRET_ACCESS_KEY: ${{ secrets.GH_AWS_SECRET_ACCESS_KEY }}

    needs:
      - publish


Release

name: Release

on:
  release:
    types: [published]

jobs:
  release-zip:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: ZIP Component Dir
        run: |
          cd ${{ github.workspace }}/custom_components/xiaomi_miot
          zip -r xiaomi_miot.zip ./

      - name: Upload zip to release
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: ${{ github.workspace }}/custom_components/xiaomi_miot/xiaomi_miot.zip
          asset_name: xiaomi_miot.zip
          tag: ${{ github.ref }}
          overwrite: true


Validate

name: Validate

on:
  push:
  pull_request:

jobs:
  validate-hassfest:
    runs-on: ubuntu-latest
    steps:
      - name: 📥 Checkout the repository
        uses: actions/checkout@v3.5.3

      - name: 🏃 Hassfest validation
        uses: home-assistant/actions/hassfest@master

  validate-hacs:
    runs-on: ubuntu-latest
    name: With HACS Action
    steps:
      - name: 📥 Checkout the repository
        uses: actions/checkout@v3.5.3

      - name: 🏃 HACS validation
        uses: hacs/action@main
        with:
          category: integration
          ignore: hacs

  validate-homeassistant:
    name: With Home Assistant
    strategy:
      matrix:
        channel: [stable, dev, "2023.1.0"]
    runs-on: ubuntu-latest
    steps:
      - name: 📥 Checkout the repository
        uses: actions/checkout@v3.5.3

      - name: 🛠 Home Assistant configuration
        run: |
          mkdir ./test_configuration
          cp -r ./custom_components ./test_configuration
          echo "default_config:" >> ./test_configuration/configuration.yaml
          echo "xiaomi_miot:" >> ./test_configuration/configuration.yaml
          echo "  language: zh" >> ./test_configuration/configuration.yaml
          echo "  device_customizes:" >> ./test_configuration/configuration.yaml
          echo "    brand.device.model:" >> ./test_configuration/configuration.yaml
          echo "      miot_local: true" >> ./test_configuration/configuration.yaml

      - name: 🏡 Home Assistant validation
        id: homeassistant
        uses: ludeeus/setup-homeassistant@main
        with:
          tag: ${{ matrix.channel }}
          config-dir: test_configuration


Assign

name: Assign
on:
  issue_comment:
    types: created

# Restrict the permissions granted to the use of secrets.GITHUB_TOKEN in this
# github actions workflow:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication
permissions:
  issues: write

jobs:
  one:
    runs-on: ubuntu-latest
    # Note that string comparisons is not case sensitive.
    if: >-
       startsWith(github.event.comment.body, '/take')
       && !github.event.issue.assignee
    steps:
      - run: |
          echo "Assigning issue ${{ github.event.issue.number }} to ${{ github.event.comment.user.login }}"
          gh issue edit $ISSUE --add-assignee ${{ github.event.comment.user.login }}
          gh issue edit $ISSUE --remove-label "help wanted"
        env:
          GH_TOKEN: ${{ github.token }}
          ISSUE: ${{ github.event.issue.html_url }}


Unassign

name: Unassign
#Runs when a contributor has unassigned themselves from the issue and adds 'help wanted'
on:
  issues:
    types: unassigned

# Restrict the permissions granted to the use of secrets.GITHUB_TOKEN in this
# github actions workflow:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication
permissions:
  issues: write

jobs:
  one:
    runs-on: ubuntu-latest
    steps:
      - name:
        if: github.event.issue.state == 'open'
        run: |
          echo "Marking issue ${{ github.event.issue.number }} as help wanted"
          gh issue edit $ISSUE --add-label "help wanted"
        env:
          GH_TOKEN: ${{ github.token }}
          ISSUE: ${{ github.event.issue.html_url }}


Wheel builder

# Workflow to build and test wheels
name: Wheel builder

on:
  schedule:
    # Nightly build at 3:42 A.M.
    - cron: "42 3 */1 * *"
  push:
    branches:
      - main
      # Release branches
      - "[0-9]+.[0-9]+.X"
  pull_request:
    branches:
      - main
      - "[0-9]+.[0-9]+.X"
  # Manual run
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

jobs:
  # Check whether to build the wheels and the source tarball
  check_build_trigger:
    name: Check build trigger
    runs-on: ubuntu-latest
    if: github.repository == 'scikit-learn/scikit-learn'
    outputs:
      build: ${{ steps.check_build_trigger.outputs.build }}

    steps:
      - name: Checkout scikit-learn
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - id: check_build_trigger
        name: Check build trigger
        run: bash build_tools/github/check_build_trigger.sh

  # Build the wheels for Linux, Windows and macOS for Python 3.9 and newer
  build_wheels:
    name: Build wheel for cp${{ matrix.python }}-${{ matrix.platform_id }}-${{ matrix.manylinux_image }}
    runs-on: ${{ matrix.os }}
    needs: check_build_trigger
    if: needs.check_build_trigger.outputs.build

    strategy:
      # Ensure that a wheel builder finishes even if another fails
      fail-fast: false
      matrix:
        include:
          # Window 64 bit
          - os: windows-latest
            python: 39
            platform_id: win_amd64
          - os: windows-latest
            python: 310
            platform_id: win_amd64
          - os: windows-latest
            python: 311
            platform_id: win_amd64
          - os: windows-latest
            python: 312
            platform_id: win_amd64

          # Linux 64 bit manylinux2014
          - os: ubuntu-latest
            python: 39
            platform_id: manylinux_x86_64
            manylinux_image: manylinux2014

          # NumPy on Python 3.10 only supports 64bit and is only available with manylinux2014
          - os: ubuntu-latest
            python: 310
            platform_id: manylinux_x86_64
            manylinux_image: manylinux2014

          - os: ubuntu-latest
            python: 311
            platform_id: manylinux_x86_64
            manylinux_image: manylinux2014
          - os: ubuntu-latest
            python: 312
            platform_id: manylinux_x86_64
            manylinux_image: manylinux2014
          - os: ubuntu-latest
            python: 313t
            platform_id: manylinux_x86_64
            manylinux_image: manylinux2014
            # TODO: remove next line when Python 3.13 is released
            prerelease_pythons: True
            free_threaded_support: True

          # MacOS x86_64
          - os: macos-12
            python: 39
            platform_id: macosx_x86_64
          - os: macos-12
            python: 310
            platform_id: macosx_x86_64
          - os: macos-12
            python: 311
            platform_id: macosx_x86_64
          - os: macos-12
            python: 312
            platform_id: macosx_x86_64

          # MacOS arm64
          - os: macos-14
            python: 39
            platform_id: macosx_arm64
          - os: macos-14
            python: 310
            platform_id: macosx_arm64
          - os: macos-14
            python: 311
            platform_id: macosx_arm64
          - os: macos-14
            python: 312
            platform_id: macosx_arm64

    steps:
      - name: Checkout scikit-learn
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11" # update once build dependencies are available

      - name: Install conda for macos arm64
        if: ${{ matrix.platform_id == 'macosx_arm64' }}
        run: |
          set -ex
          # macos arm64 runners do not have conda installed. Thus we much install conda manually
          EXPECTED_SHA="dd832d8a65a861b5592b2cf1d55f26031f7c1491b30321754443931e7b1e6832"
          MINIFORGE_URL="https://github.com/conda-forge/miniforge/releases/download/23.11.0-0/Mambaforge-23.11.0-0-MacOSX-arm64.sh"
          curl -L --retry 10 $MINIFORGE_URL -o miniforge.sh

          # Check SHA
          file_sha=$(shasum -a 256 miniforge.sh | awk '{print $1}')
          if [ "$EXPECTED_SHA" != "$file_sha" ]; then
              echo "SHA values did not match!"
              exit 1
          fi

          # Install miniforge
          MINIFORGE_PATH=$HOME/miniforge
          bash ./miniforge.sh -b -p $MINIFORGE_PATH
          echo "$MINIFORGE_PATH/bin" >> $GITHUB_PATH
          echo "CONDA_HOME=$MINIFORGE_PATH" >> $GITHUB_ENV

      - name: Set conda environment for non-macos arm64 environments
        if: ${{ matrix.platform_id != 'macosx_arm64' }}
        run: |
          # Non-macos arm64 envrionments already have conda installed
          echo "CONDA_HOME=/usr/local/miniconda" >> $GITHUB_ENV

      - name: Build and test wheels
        env:
          CIBW_PRERELEASE_PYTHONS: ${{ matrix.prerelease_pythons }}
          CIBW_FREE_THREADED_SUPPORT: ${{ matrix.free_threaded_support }}
          CIBW_ENVIRONMENT: SKLEARN_SKIP_NETWORK_TESTS=1
            SKLEARN_BUILD_PARALLEL=3
          CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }}
          CIBW_ARCHS: all
          CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux_image }}
          CIBW_MANYLINUX_I686_IMAGE: ${{ matrix.manylinux_image }}
          # Needed on Windows CI to compile with Visual Studio compiler
          # otherwise Meson detects a MINGW64 platform and use MINGW64
          # toolchain
          CIBW_CONFIG_SETTINGS_WINDOWS: "setup-args=--vsenv"
          CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: bash build_tools/github/repair_windows_wheels.sh {wheel} {dest_dir}
          CIBW_BEFORE_TEST_WINDOWS: bash build_tools/github/build_minimal_windows_image.sh ${{ matrix.python }}
          CIBW_TEST_REQUIRES: pytest
          CIBW_BEFORE_TEST: bash {project}/build_tools/wheels/cibw_before_test.sh
          CIBW_TEST_COMMAND: bash {project}/build_tools/wheels/test_wheels.sh
          CIBW_TEST_COMMAND_WINDOWS: bash {project}/build_tools/github/test_windows_wheels.sh ${{ matrix.python }}
          CIBW_BUILD_VERBOSITY: 1

        run: bash build_tools/wheels/build_wheels.sh

      - name: Store artifacts
        uses: actions/upload-artifact@v4
        with:
          name: cibw-wheels-cp${{ matrix.python }}-${{ matrix.platform_id }}
          path: wheelhouse/*.whl

  update-tracker:
    uses: ./.github/workflows/update_tracking_issue.yml
    if: ${{ always() }}
    needs: [build_wheels]
    with:
      job_status: ${{ needs.build_wheels.result }}
    secrets:
      BOT_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

  # Build the source distribution under Linux
  build_sdist:
    name: Source distribution
    runs-on: ubuntu-latest
    needs: check_build_trigger
    if: needs.check_build_trigger.outputs.build

    steps:
      - name: Checkout scikit-learn
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.9" # update once build dependencies are available

      - name: Build source distribution
        run: bash build_tools/github/build_source.sh
        env:
          SKLEARN_BUILD_PARALLEL: 3

      - name: Test source distribution
        run: bash build_tools/github/test_source.sh
        env:
          SKLEARN_SKIP_NETWORK_TESTS: 1

      - name: Store artifacts
        uses: actions/upload-artifact@v4
        with:
          name: cibw-sdist
          path: dist/*.tar.gz

  # Upload the wheels and the source distribution
  upload_anaconda:
    name: Upload to Anaconda
    runs-on: ubuntu-latest
    environment: upload_anaconda
    needs: [build_wheels, build_sdist]
    # The artifacts cannot be uploaded on PRs
    if: github.event_name != 'pull_request'

    steps:
      - name: Checkout scikit-learn
        uses: actions/checkout@v4

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: cibw-*
          path: dist
          merge-multiple: true

      - name: Setup Python
        uses: actions/setup-python@v5

      - name: Upload artifacts
        env:
          # Secret variables need to be mapped to environment variables explicitly
          SCIKIT_LEARN_NIGHTLY_UPLOAD_TOKEN: ${{ secrets.SCIKIT_LEARN_NIGHTLY_UPLOAD_TOKEN }}
          SCIKIT_LEARN_STAGING_UPLOAD_TOKEN: ${{ secrets.SCIKIT_LEARN_STAGING_UPLOAD_TOKEN }}
          ARTIFACTS_PATH: dist
        # Force a replacement if the remote file already exists
        run: bash build_tools/github/upload_anaconda.sh


Pull Request Labeler

name: "Pull Request Labeler"
on:
  pull_request_target:
    types: [opened]

# Restrict the permissions granted to the use of secrets.GITHUB_TOKEN in this
# github actions workflow:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication
permissions:
  contents: read
  pull-requests: write

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
    - uses: thomasjpfan/labeler@v2.5.1
      continue-on-error: true
      if: github.repository == 'scikit-learn/scikit-learn'
      with:
        repo-token: "${{ secrets.GITHUB_TOKEN }}"
        max-labels: "3"
        configuration-path: ".github/labeler-module.yml"

  triage_file_extensions:
    runs-on: ubuntu-latest
    steps:
    - uses: thomasjpfan/labeler@v2.5.1
      continue-on-error: true
      if: github.repository == 'scikit-learn/scikit-learn'
      with:
        repo-token: "${{ secrets.GITHUB_TOKEN }}"
        configuration-path: ".github/labeler-file-extensions.yml"


Pull Request Regex Title Labeler

name: Pull Request Regex Title Labeler
on:
  pull_request_target:
    types: [opened, edited]

# Restrict the permissions granted to the use of secrets.GITHUB_TOKEN in this
# github actions workflow:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication
permissions:
  contents: read
  pull-requests: write

jobs:

  labeler:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: '3.9'
    - name: Install PyGithub
      run: pip install -Uq PyGithub
    - name: Label pull request
      run: python .github/scripts/label_title_regex.py
      env:
        CONTEXT_GITHUB: ${{ toJson(github) }}


Publish to Pypi

name: Publish to Pypi
on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version upload to pypi'
        required: true
      pypi_repo:
        description: 'Repo to upload to (testpypi or pypi)'
        default: 'testpypi'
        required: true

jobs:
  publish:
    runs-on: ubuntu-latest
    environment: publish_pypi
    permissions:
      # IMPORTANT: this permission is mandatory for trusted publishing
      id-token: write
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: '3.8'
    - name: Install dependencies
      run: |
        pip install -U wheelhouse_uploader pyyaml
    - name: Downloading wheels and sdist from staging
      env:
        SKLEARN_VERSION: ${{ github.event.inputs.version }}
      run: |
        echo "Download $SKLEARN_VERSION wheels and sdist"
        python -m wheelhouse_uploader fetch \
          --version $SKLEARN_VERSION \
          --local-folder dist/ \
          scikit-learn \
          https://pypi.anaconda.org/scikit-learn-wheels-staging/simple/scikit-learn/
    - name: Check dist has the correct number of artifacts
      run: |
        python build_tools/github/check_wheels.py
    - name: Publish package to TestPyPI
      uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
      with:
        repository-url: https://test.pypi.org/legacy/
        print-hash: true
      if: ${{ github.event.inputs.pypi_repo == 'testpypi' }}
    - name: Publish package to PyPI
      uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14
      if: ${{ github.event.inputs.pypi_repo == 'pypi' }}
      with:
        print-hash: true


Check Changelog

name: Check Changelog
# This check makes sure that the changelog is properly updated
# when a PR introduces a change in a test file.
# To bypass this check, label the PR with "No Changelog Needed".
on:
  pull_request:
    types: [opened, edited, labeled, unlabeled, synchronize]

jobs:
  check:
    name: A reviewer will let you know if it is required or can be bypassed
    runs-on: ubuntu-latest
    if: ${{ contains(github.event.pull_request.labels.*.name, 'No Changelog Needed') == 0 }}
    steps:
      - name: Get PR number and milestone
        run: |
          echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
          echo "TAGGED_MILESTONE=${{ github.event.pull_request.milestone.title }}" >> $GITHUB_ENV
      - uses: actions/checkout@v4
        with:
          fetch-depth: '0'
      - name: Check the changelog entry
        run: |
          set -xe
          changed_files=$(git diff --name-only origin/main)
          # Changelog should be updated only if tests have been modified
          if [[ ! "$changed_files" =~ tests ]]
          then
            exit 0
          fi
          all_changelogs=$(cat ./doc/whats_new/v*.rst)
          if [[ "$all_changelogs" =~ :pr:\`$PR_NUMBER\` ]]
          then
            echo "Changelog has been updated."
            # If the pull request is milestoned check the correspondent changelog
            if exist -f ./doc/whats_new/v${TAGGED_MILESTONE:0:4}.rst
            then
              expected_changelog=$(cat ./doc/whats_new/v${TAGGED_MILESTONE:0:4}.rst)
              if [[ "$expected_changelog" =~ :pr:\`$PR_NUMBER\` ]]
              then
                echo "Changelog and milestone correspond."
              else
                echo "Changelog and milestone do not correspond."
                echo "If you see this error make sure that the tagged milestone for the PR"
                echo "and the edited changelog filename properly match."
                exit 1
              fi
            fi
          else
            echo "A Changelog entry is missing."
            echo ""
            echo "Please add an entry to the changelog at 'doc/whats_new/v*.rst'"
            echo "to document your change assuming that the PR will be merged"
            echo "in time for the next release of scikit-learn."
            echo ""
            echo "Look at other entries in that file for inspiration and please"
            echo "reference this pull request using the ':pr:' directive and"
            echo "credit yourself (and other contributors if applicable) with"
            echo "the ':user:' directive."
            echo ""
            echo "If you see this error and there is already a changelog entry,"
            echo "check that the PR number is correct."
            echo ""
            echo "If you believe that this PR does not warrant a changelog"
            echo "entry, say so in a comment so that a maintainer will label"
            echo "the PR with 'No Changelog Needed' to bypass this check."
            exit 1
          fi


Labels Blank issues

name: Labels Blank issues

on:
  issues:
    types: [opened]

jobs:
  label-blank-issues:
    runs-on: ubuntu-latest
    steps:
      - uses: andymckay/labeler@1.0.4
        with:
          add-labels: "Needs Triage"
          ignore-if-labeled: true


Update tracking issue

# For workflows to use this workflow include the following:
#
# update-tracker:
#   uses: ./.github/workflows/update_tracking_issue.yml
#   if: ${{ always() }}
#   needs: [JOB_NAME]
#   with:
#     job_status: ${{ needs.JOB_NAME.result }}
#   secrets:
#     BOT_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
# Where JOB_NAME is contains the status of the job you are interested in

name: "Update tracking issue"
on:
  workflow_call:
    inputs:
      job_status:
        required: true
        type: string
    secrets:
      BOT_GITHUB_TOKEN:
        required: true

jobs:
  update_tracking_issue:
    runs-on: ubuntu-latest
    if: github.repository == 'scikit-learn/scikit-learn' && github.event_name == 'schedule'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.9'
      - name: Update tracking issue on GitHub
        run: |
          set -ex
          if [[ ${{ inputs.job_status }} == "success" ]]; then
            TESTS_PASSED=true
          else
            TESTS_PASSED=false
          fi

          pip install defusedxml PyGithub
          python maint_tools/update_tracking_issue.py \
            ${{ secrets.BOT_GITHUB_TOKEN }} \
            "$GITHUB_WORKFLOW" \
            "$GITHUB_REPOSITORY" \
            https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID \
            --tests-passed $TESTS_PASSED


CircleCI artifacts redirector

name: CircleCI artifacts redirector
on: [status]

# Restrict the permissions granted to the use of secrets.GITHUB_TOKEN in this
# github actions workflow:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication
permissions:
  statuses: write

jobs:
  circleci_artifacts_redirector_job:
    runs-on: ubuntu-latest
    # For testing this action on a fork, remove the "github.repository =="" condition.
    if: "github.repository == 'scikit-learn/scikit-learn' && github.event.context == 'ci/circleci: doc'"
    name: Run CircleCI artifacts redirector
    steps:
      - name: GitHub Action step
        uses: scientific-python/circleci-artifacts-redirector-action@v1
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          api-token: ${{ secrets.CIRCLECI_TOKEN }}
          artifact-path: 0/doc/_changed.html
          circleci-jobs: doc
          job-title: Check the rendered docs here!


linter

# This linter job on GH actions is used to trigger the commenter bot
# in bot-lint-comment.yml file. It stores the output of the linter to be used
# by the commenter bot.
name: linter

on:
  - pull_request_target

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

jobs:
  lint:
    runs-on: ubuntu-latest

    # setting any permission will set everything else to none for GITHUB_TOKEN
    permissions:
      pull-requests: none

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

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

      - name: Install dependencies
        run: |
          source build_tools/shared.sh
          # Include pytest compatibility with mypy
          pip install pytest ruff $(get_dep mypy min) $(get_dep black min) cython-lint
          # we save the versions of the linters to be used in the error message later.
          python -c "from importlib.metadata import version; print(f\"ruff={version('ruff')}\")" >> /tmp/versions.txt
          python -c "from importlib.metadata import version; print(f\"mypy={version('mypy')}\")" >> /tmp/versions.txt
          python -c "from importlib.metadata import version; print(f\"black={version('black')}\")" >> /tmp/versions.txt
          python -c "from importlib.metadata import version; print(f\"cython-lint={version('cython-lint')}\")" >> /tmp/versions.txt

      - name: Run linting
        id: lint-script
        # We download the linting script from main, since this workflow is run
        # from main itself.
        run: |
          curl https://raw.githubusercontent.com/${{ github.repository }}/main/build_tools/linting.sh --retry 5 -o ./build_tools/linting.sh
          set +e
          ./build_tools/linting.sh &> /tmp/linting_output.txt
          cat /tmp/linting_output.txt

      - name: Upload Artifact
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: lint-log
          path: |
            /tmp/linting_output.txt
            /tmp/versions.txt
          retention-days: 1

  comment:
    needs: lint
    if: ${{ !cancelled() }}
    runs-on: ubuntu-latest

    # We need these permissions to be able to post / update comments
    permissions:
      pull-requests: write
      issues: write

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

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

      - name: Install dependencies
        run: python -m pip install requests

      - name: Download artifact
        id: download-artifact
        uses: actions/download-artifact@v4
        with:
          name: lint-log

      - name: Print log
        run: cat linting_output.txt

      - name: Process Comments
        id: process-comments
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          BRANCH_SHA: ${{ github.event.pull_request.head.sha }}
          RUN_ID: ${{ github.run_id }}
          LOG_FILE: linting_output.txt
          VERSIONS_FILE: versions.txt
        run: python ./build_tools/get_comment.py


Update lock files

# Workflow to update lock files
name: Update lock files

on:
  workflow_dispatch:
  schedule:
    - cron: '0 5 * * 1'

# XXX Set the right permissions, per step??
# Can we set read only at the global level here and then elevate to write for some steps?
#permissions: read-all

jobs:
  update_lock_files:
    if: github.repository == 'scikit-learn/scikit-learn'
    runs-on: ubuntu-latest

    strategy:
      # Ensure that each build will continue even if one build in the matrix fails
      fail-fast: false
      matrix:
        include:
          - name: main
            update_script_args: "--select-tag main-ci"
            additional_commit_message: "[doc build]"
          - name: scipy-dev
            update_script_args: "--select-tag scipy-dev"
            additional_commit_message: "[scipy-dev]"
          - name: cirrus-arm
            update_script_args: "--select-tag arm"
            additional_commit_message: "[cirrus arm]"
          - name: array-api
            update_script_args: "--select-tag cuda"

    steps:
      - uses: actions/checkout@v4
      - name: Generate lock files
        run: |
          source build_tools/shared.sh
          source $CONDA/bin/activate
          conda install -n base conda conda-libmamba-solver -y
          conda config --set solver libmamba
          conda install -c conda-forge "$(get_dep conda-lock min)" -y

          python build_tools/update_environments_and_lock_files.py ${{ matrix.update_script_args }}

      - name: Create Pull Request
        id: cpr
        uses: peter-evans/create-pull-request@v6
        with:
          token: ${{ secrets.BOT_GITHUB_TOKEN }}
          push-to-fork: scikit-learn-bot/scikit-learn
          commit-message: Update CI lock files ${{ matrix.additional_commit_message }}
          committer: "Lock file bot <noreply@github.com>"
          author: "Lock file bot <noreply@github.com>"
          delete-branch: true
          branch: auto-update-lock-files-${{ matrix.name }}
          title: ":lock: :robot: CI Update lock files for ${{ matrix.name }} CI build(s) :lock: :robot:"
          body: |
            Update lock files.

            ### Note
            If the CI tasks fail, create a new branch based on this PR and add the required fixes to that branch.

      # The CUDA workflow needs to be triggered explicitly as it uses an expensive runner
      - name: Trigger additional tests
        if: steps.cpr.outputs.pull-request-number != '' && matrix.name == 'array-api'
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh workflow run .github/workflows/cuda-gpu-ci.yml -f commit_hash=`git rev-parse HEAD`

      - name: Check Pull Request
        if: steps.cpr.outputs.pull-request-number != ''
        run: |
          echo "### :rocket: Pull-Request Summary" >> ${GITHUB_STEP_SUMMARY}
          echo "" >> ${GITHUB_STEP_SUMMARY}
          echo "The following lock files pull-request has been auto-generated:"
          echo "- **PR** #${{ steps.cpr.outputs.pull-request-number }}" >> ${GITHUB_STEP_SUMMARY}
          echo "- **URL** ${{ steps.cpr.outputs.pull-request-url }}" >> ${GITHUB_STEP_SUMMARY}
          echo "- **Operation** [${{ steps.cpr.outputs.pull-request-operation }}]" >> ${GITHUB_STEP_SUMMARY}
          echo "- **SHA** ${{ steps.cpr.outputs.pull-request-head-sha }}" >> ${GITHUB_STEP_SUMMARY}


CodeQL

name: "CodeQL"

on:
  push:
    branches: [ "main", "*.X" ]
  pull_request:
    branches: [ "main", "*.X" ]
  schedule:
    - cron: '0 6 * * 1'

jobs:
  analyze:
    name: Analyze
    # Runner size impacts CodeQL analysis time. To learn more, please see:
    #   - https://gh.io/recommended-hardware-resources-for-running-codeql
    #   - https://gh.io/supported-runners-and-hardware-resources
    #   - https://gh.io/using-larger-runners
    # Consider using larger runners for possible analysis time improvements.
    runs-on: 'ubuntu-latest'
    timeout-minutes: 360
    permissions:
      # required for all workflows
      security-events: write

      # only required for workflows in private repositories
      actions: read
      contents: read

    strategy:
      fail-fast: false
      matrix:
        language: [ 'javascript-typescript', 'python' ]
        # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ]
        # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both
        # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

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

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      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.

        # For more 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#, Go, Java, or Swift).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v3

    # ℹ 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@v3
      with:
        category: "/language:${{matrix.language}}"


Check sdist

name: "Check sdist"

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

jobs:
  check-sdist:
    # Don't run on forks
    if: github.repository == 'scikit-learn/scikit-learn'

    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.9'
      - name: Install dependencies
        # scipy and cython are required to build sdist
        run: |
          python -m pip install --upgrade pip
          pip install check-sdist
      - run: |
          check-sdist --inject-junk

  update-tracker:
    uses: ./.github/workflows/update_tracking_issue.yml
    if: ${{ always() }}
    needs: [check-sdist]
    with:
      job_status: ${{ needs.check-sdist.result }}
    secrets:
      BOT_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}


Lint

name: Lint

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: psf/black@stable
        with:
          options: "--check --verbose --include '(\\.pyi?|\\.ipynb|workbench)$'"


Build

name: Build
on:
  release:
    types: [published]
jobs:
  build:
    name: Build
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: macos-11
            TARGET: macos
            CMD_BUILD: pyinstaller workbench --onefile --name workbench-macos
            OUT_FILE_NAME: workbench-macos
            ASSET_MIME: application/zip
          - os: ubuntu-latest
            TARGET: linux
            CMD_BUILD: pyinstaller workbench --onefile --name workbench-linux
            OUT_FILE_NAME: workbench-linux
            ASSET_MIME: application/zip
          - os: windows-latest
            TARGET: windows
            CMD_BUILD: pyinstaller workbench --onefile --name workbench
            OUT_FILE_NAME: workbench.exe
            ASSET_MIME: application/vnd.microsoft.portable-executable
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python 3.8
      uses: actions/setup-python@v5
      with:
        python-version: 3.8
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install "pyinstaller" "requests>=2.22,<3" "requests_cache" "ruamel.yaml<=0.17.21" "progress_bar" "openpyxl" "unidecode" "edtf_validate" "typing-extensions" "rich"
    - name: Build for ${{matrix.TARGET}}
      run: ${{matrix.CMD_BUILD}}
    - name: Upload Release Asset
      id: upload-release-asset
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ github.event.release.upload_url }}
        asset_path: ./dist/${{ matrix.OUT_FILE_NAME}}
        asset_name: ${{ matrix.OUT_FILE_NAME}}
        asset_content_type: ${{ matrix.ASSET_MIME}}


Run unittests

name: Run unittests

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11"]

    steps:
      - uses: actions/checkout@v4
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          python setup.py install
      - name: Run unittests
        run: |
          python tests/unit_tests.py
          python tests/field_tests.py
          python tests/csv_id_to_node_id_map_tests.py


Auto approve PRs by dependabot

name: Auto approve PRs by dependabot

# Trigger the workflow on pull request
on: pull_request_target

jobs:
  autoapprove:
    name: Auto-Approve a PR by dependabot
    runs-on: ubuntu-latest
    steps:
      - name: Auto approve
        uses: cognitedata/auto-approve-dependabot-action@v3.0.1
        if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]'
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}


auto-merge

name: auto-merge
on:
  pull_request_target:

jobs:
  auto-merge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ahmadnassri/action-dependabot-auto-merge@v2
        with:
          target: minor
          github-token: ${{ secrets.SECURITY_CONTENT_ADMIN_TASKS }}


auto-update

name: auto-update
on:
  push: {}
jobs:

  validate-tag-if-present:
    runs-on: ubuntu-latest

    steps:
      - name: TAGGED, Validate that the tag is in the correct format

        run: |
          echo "The GITHUB_REF: $GITHUB_REF"
          #First check to see if the release is a tag
          if [[ $GITHUB_REF =~ refs/tags/* ]]; then
            #Yes, this is a tag, so we need to test to make sure that the tag
            #is in the correct format (like v1.10.20)
            if [[ $GITHUB_REF =~ refs/tags/v[0-9]+.[0-9]+.[0-9]+ ]]; then
              echo "PASS: Tagged release with good format"
              exit 0
            else
              echo "FAIL: Tagged release with bad format"
              exit 1
            fi
          else
          echo "PASS: Not a tagged release"
          exit 0
          fi
  autoupdate:
    name: autoupdate
    runs-on: ubuntu-latest
    steps:
      - uses: docker://chinthakagodawita/autoupdate-action:v1
        env:
          GITHUB_TOKEN: "${{ secrets.SECURITY_CONTENT_ADMIN_TASKS }}"
          DRY_RUN: "false"
          MERGE_MSG: "Branch was auto-updated."