name: CI

on:
  merge_group:
  pull_request:
    branches:
      - 'master'
  schedule:
    - cron: '44 4 * * *' # At 4:44 UTC every day.

defaults:
  run:
    shell: bash

jobs:
  test:
    name: test (${{ matrix.host_target }})
    strategy:
      fail-fast: false
      matrix:
        include:
          - host_target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - host_target: i686-unknown-linux-gnu
            os: ubuntu-latest
            multiarch: i386
            gcc_cross: i686-linux-gnu
          - host_target: aarch64-unknown-linux-gnu
            os: ubuntu-24.04-arm
          - host_target: armv7-unknown-linux-gnueabihf
            os: ubuntu-24.04-arm
            multiarch: armhf
            gcc_cross: arm-linux-gnueabihf
          - host_target: riscv64gc-unknown-linux-gnu
            os: ubuntu-latest
            multiarch: riscv64
            gcc_cross: riscv64-linux-gnu
            qemu: true
          # Ubuntu mirrors are not reliable enough for these architectures
          # (see <https://bugs.launchpad.net/ubuntu/+bug/2130309>).
          # - host_target: s390x-unknown-linux-gnu
          #   os: ubuntu-latest
          #   multiarch: s390x
          #   gcc_cross: s390x-linux-gnu
          #   qemu: true
          # - host_target: powerpc64le-unknown-linux-gnu
          #   os: ubuntu-latest
          #   multiarch: ppc64el
          #   gcc_cross: powerpc64le-linux-gnu
          #   qemu: true
          - host_target: aarch64-apple-darwin
            os: macos-latest
          - host_target: i686-pc-windows-msvc
            os: windows-latest
          - host_target: aarch64-pc-windows-msvc
            os: windows-11-arm
    runs-on: ${{ matrix.os }}
    env:
      HOST_TARGET: ${{ matrix.host_target }}
    steps:
      - uses: actions/checkout@v4
      - name: install multiarch
        if: ${{ matrix.multiarch != '' }}
        run: |
          # armhf, s390x, ppc64el, riscv64 need Ubuntu Ports to be in the mirror list
          sudo bash -c "echo 'https://ports.ubuntu.com/	priority:4' >> /etc/apt/apt-mirrors.txt"
          # Add architecture
          sudo dpkg --add-architecture ${{ matrix.multiarch }}
          # Ubuntu Ports often has outdated mirrors so try a few times to get the apt repo
          for TRY in $(seq 3); do
            { sudo apt update && break; } || sleep 60
          done
          # Install needed packages
          sudo apt install $(echo "libatomic1: zlib1g-dev:" | sed 's/:/:${{ matrix.multiarch }}/g')
      - name: install qemu
        if: ${{ matrix.qemu }}
        run: sudo apt install qemu-user qemu-user-binfmt
      - uses: ./.github/workflows/setup
        with:
          toolchain_flags: "--host ${{ matrix.host_target }}"

      # We set up the cross-compiler *after* the basic setup as setting CC would otherwise
      # cause confusion.
      - name: install gcc-cross
        if: ${{ matrix.gcc_cross != '' }}
        run: |
          sudo apt install gcc-${{ matrix.gcc_cross }}
          echo "Setting environment variables:"
          echo "CC_${{ matrix.host_target }}=${{ matrix.gcc_cross }}-gcc" | tee -a $GITHUB_ENV
          TARGET_UPPERCASE=$(echo ${{ matrix.host_target }} | tr '[:lower:]-' '[:upper:]_')
          echo "CARGO_TARGET_${TARGET_UPPERCASE}_LINKER=${{ matrix.gcc_cross }}-gcc" | tee -a $GITHUB_ENV

      # The main test job! We don't run this in qemu as that is quite slow,
      # so those targets only get the clippy check below.
      - name: test Miri
        if: ${{ !matrix.qemu }}
        run: ./ci/ci.sh

      # The `style` job only runs on Linux; this makes sure the host-specific
      # code is also covered by clippy.
      - name: clippy
        run: ./miri clippy -- -D warnings

  style:
    name: style checks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/workflows/setup

      - name: rustfmt
        run: ./miri fmt --check
      - name: clippy (no features)
        run: ./miri clippy --no-default-features -- -D warnings
      - name: clippy (all features)
        run: ./miri clippy --all-features -- -D warnings
      - name: rustdoc
        run: RUSTDOCFLAGS="-Dwarnings" ./miri doc --document-private-items

  bootstrap:
    name: bootstrap build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Deliberately skipping `./.github/workflows/setup` as we do our own setup
      - name: Add cache for cargo
        id: cache
        uses: actions/cache@v4
        with:
          path: |
            # Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>.
            # Cache package/registry information
            ~/.cargo/registry/index
            ~/.cargo/registry/cache
            ~/.cargo/git/db
            # Cache bootstrap downloads
            ../rust/build/cache
          key: cargo-bootstrap-${{ hashFiles('rust-version') }}
          restore-keys: cargo-bootstrap
      - name: prepare build environment
        run: |
          MIRIDIR=$(pwd)
          cd ..
          # Bootstrap needs at least depth 2 to function.
          git clone https://github.com/rust-lang/rust/ rust --depth 2 --revision $(cat "$MIRIDIR/rust-version")
          cd rust
          # Replace the in-tree Miri with the current version.
          rm src/tools/miri -rf
          ln -s "$MIRIDIR" src/tools/miri
      - name: check build
        run: |
          cd ../rust # ./x does not seem to like being invoked from elsewhere
          ./x check miri

  coverage:
    name: coverage report
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/workflows/setup
      - name: coverage
        run: ./miri test --coverage

  # Summary job for the merge queue.
  # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
  # And they should be added below in `cron-fail-notify` as well.
  conclusion:
    needs: [test, style, bootstrap, coverage]
    # We need to ensure this job does *not* get skipped if its dependencies fail,
    # because a skipped job is considered a success by GitHub. So we have to
    # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
    # when the workflow is canceled manually.
    if: ${{ !cancelled() }}
    runs-on: ubuntu-latest
    steps:
      # Manually check the status of all dependencies. `if: failure()` does not work.
      - name: Conclusion
        run: |
            # Print the dependent jobs to see them in the CI log
            jq -C <<< '${{ toJson(needs) }}'
            # Check if all jobs that we depend on (in the needs array) were successful.
            jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'

  cron-rustc-pull:
    name: automatic pull from rustc
    runs-on: ubuntu-latest
    permissions:
        # The cronjob needs to be able to push to the repo...
        contents: write
        # ... and create a PR.
        pull-requests: write
    if: ${{ github.event_name == 'schedule' }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 256 # get a bit more of the history
      - name: install josh-sync
        run: cargo +stable install --locked --git https://github.com/rust-lang/josh-sync
      - name: setup bot git name and email
        run: |
          git config --global user.name 'The Miri Cronjob Bot'
          git config --global user.email 'miri@cron.bot'
      - name: Install nightly toolchain
        run: rustup toolchain install nightly --profile minimal
      - name: Install rustup-toolchain-install-master
        run: cargo install -f rustup-toolchain-install-master
      # Create a token for the next step so it can create a PR that actually runs CI.
      - uses: actions/create-github-app-token@v2
        id: app-token
        with:
          app-id: ${{ vars.APP_CLIENT_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
      - name: pull changes from rustc and create PR
        run: |
          # Make it easier to see what happens.
          set -x
          # Temporarily disable early exit to examine the status code of rustc-josh-sync
          set +e
          rustc-josh-sync pull
          exitcode=$?
          set -e

          # If there were no changes to pull, rustc-josh-sync returns status code 2.
          # In that case, skip the rest of the job.
          if [ $exitcode -eq 2 ]; then
            echo "Nothing changed in rustc, skipping PR"
            exit 0
          elif [ $exitcode -ne 0 ]; then
            # If return code was not 0 or 2, rustc-josh-sync actually failed
            echo "error: rustc-josh-sync failed"
            exit ${exitcode}
          fi

          # Store merge commit message          
          git log -1 --pretty=%B > message.txt

          # Format changes
          ./miri toolchain
          ./miri fmt --check || (./miri fmt && git commit -am "fmt")

          # Create a PR
          BRANCH="rustup-$(date -u +%Y-%m-%d)"
          git switch -c $BRANCH
          git push -u origin $BRANCH
          gh pr create -B master --title 'Automatic Rustup' --body-file message.txt
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

  cron-fail-notify:
    name: cronjob failure notification
    runs-on: ubuntu-latest
    needs: [test, style, bootstrap, coverage]
    if: ${{ github.event_name == 'schedule' && failure() }}
    steps:
      # Send a Zulip notification
      - name: Install zulip-send
        run: pip3 install zulip
      - name: Send Zulip notification
        env:
          ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }}
          ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }}
        run: |
          ~/.local/bin/zulip-send --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com \
            --stream miri --subject "Miri Build Failure ($(date -u +%Y-%m))" \
            --message 'Dear @*T-miri*,

          It would appear that the [Miri cron job build]('"https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"') failed.

          This likely means that rustc changed the miri directory and
          we now need to do a [`rustc-josh-sync pull`](https://github.com/rust-lang/miri/blob/master/CONTRIBUTING.md#importing-changes-from-the-rustc-repo).

          Would you mind investigating this issue?

          Thanks in advance!
          Sincerely,
          The Miri Cronjobs Bot'
