on:
  schedule:
    # 6:30 UTC Sunday
    - cron: '30 6 * * 0'

  pull_request:
    paths:
      - 'installers-conda/**'
      - '.github/workflows/installers-conda.yml'
      - '.github/scripts/installer_test.sh'
      - 'requirements/*.yml'
      - 'MANIFEST.in'

  release:
    types:
      - created

  workflow_dispatch:
    inputs:
      pre:
        description: 'Build as release candidate'
        required: false
        default: true
        type: boolean
      ssh:
        description: 'Enable ssh debugging'
        required: false
        default: false
        type: boolean
      macos-x86_64:
        description: 'Build macOS x86_64 installer'
        required: false
        default: true
        type: boolean
      macos-arm64:
        description: 'Build macOS arm64 installer'
        required: false
        default: true
        type: boolean
      linux:
        description: 'Build Linux installer'
        required: false
        default: true
        type: boolean
      win:
        description: 'Build Windows installer'
        required: false
        default: true
        type: boolean

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

name: Weekly conda-based installers

env:
  IS_RELEASE: ${{ github.event_name == 'release' }}
  IS_PRE_RELEASE: ${{ github.event_name == 'workflow_dispatch' && inputs.pre }}
  ENABLE_SSH: ${{ github.event_name == 'workflow_dispatch' && inputs.ssh }}
  BUILD_MAC: ${{ github.event_name != 'workflow_dispatch' || inputs.macos-x86_64 }}
  BUILD_ARM: ${{ github.event_name != 'workflow_dispatch' || inputs.macos-arm64 }}
  BUILD_LNX: ${{ github.event_name != 'workflow_dispatch' || inputs.linux }}
  BUILD_WIN: ${{ github.event_name != 'workflow_dispatch' || inputs.win }}
  USE_SUBREPOS: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && ! inputs.pre) }}
  NOTARIZE: ${{ github.event_name == 'schedule' || github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.pre) }}

jobs:
  build-matrix:
    name: Determine Build Matrix
    runs-on: ubuntu-latest
    outputs:
      target_platform: ${{ steps.build-matrix.outputs.target_platform }}
      branch: ${{ steps.build-matrix.outputs.branch }}
      include: ${{ steps.build-matrix.outputs.include }}
      python_version: ${{ steps.build-matrix.outputs.python_version }}

    steps:
    - name: Determine Build Matrix
      id: build-matrix
      run: |
        if [[ $BUILD_MAC == "true" ]]; then
            target_platform="'osx-64'"
            include="{'os': 'macos-13', 'target-platform': 'osx-64', 'spyk-arch': 'unix'}"
        fi
        if [[ $BUILD_ARM == "true" ]]; then
            target_platform=${target_platform:+"$target_platform, "}"'osx-arm64'"
            include=${include:+"$include, "}"{'os': 'macos-14', 'target-platform': 'osx-arm64', 'spyk-arch': 'unix'}"
        fi
        if [[ $BUILD_LNX == "true" ]]; then
            target_platform=${target_platform:+"$target_platform, "}"'linux-64'"
            include=${include:+"$include, "}"{'os': 'ubuntu-latest', 'target-platform': 'linux-64', 'spyk-arch': 'unix'}"
        fi
        if [[ $BUILD_WIN == "true" ]]; then
            target_platform=${target_platform:+"$target_platform, "}"'win-64'"
            include=${include:+"$include, "}"{'os': 'windows-latest', 'target-platform': 'win-64', 'spyk-arch': 'win-64'}"
        fi

        if [[ $GITHUB_EVENT_NAME == "schedule" ]]; then
            branch="'master', '6.x'"
        else
            branch="''"
        fi

        echo "target_platform=[$target_platform]" >> $GITHUB_OUTPUT
        echo "branch=[$branch]" >> $GITHUB_OUTPUT
        echo "include=[$include]" >> $GITHUB_OUTPUT

  build-subrepos:
    name: Build Subrepos
    # env.USE_SUBREPOS is not available at job level; must copy-paste here
    if: github.event_name == 'schedule' || github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && ! inputs.pre)
    needs:
      - build-matrix
    strategy:
      fail-fast: false
      matrix:
        branch: ${{ fromJson(needs.build-matrix.outputs.branch) }}
    uses: ./.github/workflows/build-subrepos.yml
    with:
      branch: ${{ matrix.branch }}

  build-installers:
    name: Build installer
    if: ${{ ! failure() && ! cancelled() }}
    runs-on: ${{ matrix.os }}
    needs:
      - build-matrix
      - build-subrepos
    strategy:
      fail-fast: false
      matrix:
        target-platform: ${{ fromJson(needs.build-matrix.outputs.target_platform) }}
        python-version: ['3.11']
        branch: ${{ fromJson(needs.build-matrix.outputs.branch) }}
        include: ${{ fromJson(needs.build-matrix.outputs.include) }}
    defaults:
      run:
        shell: bash -le {0}
        working-directory: ${{ github.workspace }}/installers-conda
    env:
      DISTDIR: ${{ github.workspace }}/installers-conda/dist
      MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
      MACOS_INSTALLER_CERTIFICATE: ${{ secrets.MACOS_INSTALLER_CERTIFICATE }}
      APPLICATION_PWD: ${{ secrets.APPLICATION_PWD }}
      CONSTRUCTOR_TARGET_PLATFORM: ${{ matrix.target-platform }}
      MATRIX_BRANCH: ${{ matrix.branch }}

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ matrix.branch }}

      - name: Setup Remote SSH Connection
        if: env.ENABLE_SSH == 'true'
        uses: mxschmitt/action-tmate@v3
        timeout-minutes: 60
        with:
          detached: true

      - name: Restore python-lsp-server Cache
        if: env.USE_SUBREPOS == 'true'
        uses: actions/cache/restore@v4
        with:
          path: installers-conda/build/conda-bld/**/*.tar.bz2
          key: python-lsp-server_noarch_${{ matrix.python-version }}_${{ hashFiles('external-deps/python-lsp-server/.gitrepo') }}
          enableCrossOsArchive: true
          fail-on-cache-miss: true

      - name: Restore qtconsole Cache
        if: env.USE_SUBREPOS == 'true'
        uses: actions/cache/restore@v4
        with:
          path: installers-conda/build/conda-bld/**/*.tar.bz2
          key: qtconsole_noarch_${{ matrix.python-version }}_${{ hashFiles('external-deps/qtconsole/.gitrepo') }}
          enableCrossOsArchive: true
          fail-on-cache-miss: true

      - name: Restore ${{ matrix.spyk-arch }} spyder-kernels Cache
        if: env.USE_SUBREPOS == 'true'
        uses: actions/cache/restore@v4
        with:
          path: installers-conda/build/conda-bld/**/*.tar.bz2
          key: spyder-kernels_${{ matrix.spyk-arch }}_${{ matrix.python-version }}_${{ hashFiles('external-deps/spyder-kernels/.gitrepo') }}
          enableCrossOsArchive: true
          fail-on-cache-miss: true

      - name: Setup Build Environment (Windows)
        if: runner.os == 'Windows'
        uses: mamba-org/setup-micromamba@v1
        with:
          condarc: |
            conda_build:
              pkg_format: '2'
              zstd_compression_level: '19'
            channels:
              - conda-forge/label/spyder_rc
              - conda-forge/label/spyder_dev
              - conda-forge/label/spyder_kernels_rc
              # TODO: Add this channel when we create it
              # - conda-forge/label/spyder_kernels_dev
              - conda-forge
          environment-file: installers-conda/build-environment.yml
          environment-name: spy-inst
          create-args: >-
            --channel-priority=flexible
            python=${{ matrix.python-version }}
            nsis>=3.08=*_log_*
          cache-downloads: true
          cache-environment: true

      - name: Setup Build Environment (macOS & Linux)
        if: runner.os != 'Windows'
        uses: mamba-org/setup-micromamba@v1
        with:
          condarc: |
            conda_build:
              pkg_format: '2'
              zstd_compression_level: '19'
            channels:
              - conda-forge/label/spyder_rc
              - conda-forge/label/spyder_dev
              - conda-forge/label/spyder_kernels_rc
              # TODO: Add this channel when we create it
              # - conda-forge/label/spyder_kernels_dev
              - conda-forge
          environment-file: installers-conda/build-environment.yml
          environment-name: spy-inst
          create-args: >-
            --channel-priority=flexible
            python=${{ matrix.python-version }}
          cache-downloads: true
          cache-environment: true

      - name: Env Variables
        run: |
          if [[ "$IS_RELEASE" != "true" && "$IS_PRE_RELEASE" != "true" ]]; then
              NSIS_USING_LOG_BUILD=1
              echo "NSIS_USING_LOG_BUILD=$NSIS_USING_LOG_BUILD" >> $GITHUB_ENV
          fi

          CONDA_BLD_PATH=${RUNNER_TEMP}/conda-bld
          echo "CONDA_BLD_PATH=$CONDA_BLD_PATH" >> $GITHUB_ENV

          env | sort

      - name: Build ${{ matrix.target-platform }} spyder Conda Package
        if: env.IS_RELEASE == 'false'
        run: |
          # Copy built packages to new build location because spyder cannot be
          # built in workspace
          [[ -d build/conda-bld ]] && cp -Rv build/conda-bld $CONDA_BLD_PATH

          python build_conda_pkgs.py --build spyder

      - name: Create Local Conda Channel
        run: |
          mkdir -p $CONDA_BLD_PATH
          conda config --set bld_path $CONDA_BLD_PATH
          conda index $CONDA_BLD_PATH
          mamba search -c local --override-channels || true

      - name: Create Keychain
        if: runner.os == 'macOS' && env.NOTARIZE == 'true'
        run: |
          _codesign=$(which codesign)
          if [[ $_codesign =~ ${CONDA_PREFIX}.* ]]; then
              # Find correct codesign
              echo "Moving $_codesign..."
              mv $_codesign ${_codesign}.bak
          fi

          ./certkeychain.sh "${MACOS_CERTIFICATE_PWD}" "${MACOS_CERTIFICATE}" "${MACOS_INSTALLER_CERTIFICATE}"
          CNAME=$(security find-identity -p codesigning -v | pcre2grep -o1 "\(([0-9A-Z]+)\)")
          echo "CNAME=$CNAME" >> $GITHUB_ENV

      - name: Load signing certificate (Windows)
        if: runner.os == 'Windows' && env.NOTARIZE == 'true'
        run: |
          echo "${MACOS_CERTIFICATE}" > "${{ runner.temp }}/certificate.b64.txt"
          certutil.exe -decode "${{ runner.temp }}/certificate.b64.txt" "${{ runner.temp }}/certificate.pfx"

          echo "CONSTRUCTOR_SIGNING_CERTIFICATE=${{ runner.temp }}/certificate.pfx" >> $GITHUB_ENV
          echo "CONSTRUCTOR_PFX_CERTIFICATE_PASSWORD=${MACOS_CERTIFICATE_PWD}" >> $GITHUB_ENV
          echo "CONSTRUCTOR_SIGNTOOL_PATH=C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe" >> $GITHUB_ENV

      - name: Build Package Installer
        run: |
          [[ -n $CNAME ]] && args=("--cert-id" "$CNAME") || args=()
          python build_installers.py ${args[@]}

          SPYVER=$(python build_installers.py --version)
          PKG_PATH=$(python build_installers.py --artifact-name)
          PKG_NAME=$(basename $PKG_PATH)
          ARTIFACT_NAME=${PKG_NAME%.*}
          [[ "$GITHUB_EVENT_NAME" == "schedule" ]] && ARTIFACT_NAME=$ARTIFACT_NAME-$MATRIX_BRANCH

          echo "SPYVER=$SPYVER" >> $GITHUB_ENV
          echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV
          echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV
          echo "PKG_PATH=$PKG_PATH" >> $GITHUB_ENV

      - name: Test macOS or Linux Installer
        if: runner.os != 'Windows'
        run: ${{ github.workspace }}/.github/scripts/installer_test.sh

      - name: Test Windows Installer
        if: runner.os == 'Windows'
        shell: cmd
        run: |
          set base_prefix=%USERPROFILE%\AppData\Local\spyder-6
          start /wait %PKG_PATH% /InstallationType=JustMe /NoRegistry=1 /S

          echo.
          if exist %base_prefix%\install.log (
              echo Log output:
              type %base_prefix%\install.log
          ) else (
              echo No log found at %base_prefix%\install.log
          )

          set mode=system
          for /F "tokens=*" %%i in (
              '%base_prefix%\python %base_prefix%\Scripts\menuinst_cli.py shortcut --mode=%mode%'
          ) do (
              set shortcut=%%~fi
          )
          echo.
          if exist "%shortcut%" (
              echo Spyder installed successfully
          ) else (
              echo Spyder NOT installed successfully
              EXIT /B 1
          )

          set runtime_python=%base_prefix%\envs\spyder-runtime\python
          for /F "tokens=*" %%i in (
              '%runtime_python% -c "import spyder; print(spyder.__version__)"'
          ) do (
              set actual_version=%%~i
          )
          echo.
          echo Expected version = %SPYVER%
          echo Actual version   = %actual_version%
          if %SPYVER% neq %actual_version% (
              echo Error: installed Spyder version is incorrect!
              EXIT /B 1
          )

          EXIT /B %ERRORLEVEL%

      - name: Notarize or Compute Checksum
        if: env.NOTARIZE == 'true'
        run: |
          if [[ $RUNNER_OS == "macOS" ]]; then
              ./notarize.sh -p $APPLICATION_PWD $PKG_PATH
          else
              cd $DISTDIR
              echo $(sha256sum $PKG_NAME) > "${ARTIFACT_NAME}-sha256sum.txt"
          fi

      - name: Upload Artifact
        uses: actions/upload-artifact@v4
        with:
          path: ${{ env.DISTDIR }}
          name: ${{ env.ARTIFACT_NAME }}

  upload-assets:
    name: Upload Assets
    if: ${{ ! failure() && ! cancelled() }}
    runs-on: ubuntu-latest
    needs:
      - build-installers
    defaults:
      run:
        shell: bash -le {0}

    steps:
      - name: Download Assets
        uses: actions/download-artifact@v4
        with:
          merge-multiple: 'true'

      - name: Zip Lock Files
        run: zip -mT spyder-conda-lock *.lock

      - name: Upload Artifact
        uses: actions/upload-artifact@v4
        with:
          path: spyder-conda-lock.zip
          name: spyder-conda-lock

      - name: Get Release
        if: env.IS_RELEASE == 'true'
        uses: bruceadams/get-release@v1.3.2
        id: get_release
        env:
          GITHUB_TOKEN: ${{ github.token }}

      - name: Upload Release Asset
        if: env.IS_RELEASE == 'true'
        uses: shogo82148/actions-upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ github.token }}
        with:
          upload_url: ${{ steps.get_release.outputs.upload_url }}
          asset_path: ${{ github.workspace }}/*.*
