name: CI

on:
  merge_group:
  pull_request:
    branches:
      - libc-0.2

env:
  CARGO_TERM_COLOR: always
  CARGO_TERM_VERBOSE: true
  LIBC_CI: 1
  RUSTDOCFLAGS: -Dwarnings
  RUSTFLAGS: -Dwarnings
  RUST_BACKTRACE: full

defaults:
  run:
    shell: bash

jobs:
  style_check:
    name: Style check
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: Setup Rust toolchain
        run: ./ci/install-rust.sh && rustup component add rustfmt
      - name: Check style
        run: ./ci/style.sh

  clippy:
    name: Clippy on ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-24.04, macos-14, windows-2022]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - run: rustup update stable --no-self-update
      - uses: Swatinem/rust-cache@v2
      # Here we use the latest stable Rust toolchain already installed by GitHub
      # Ideally we should run it for every target, but we cannot rely on unstable toolchains
      # due to Clippy not being consistent between them.
      - run: cargo clippy --workspace --exclude libc-test --exclude ctest-test --all-targets -- -D warnings

  # This runs `cargo build --target ...` for all T1 and T2 targets`
  verify_build:
    name: Verify build
    strategy:
      matrix:
        toolchain: [stable, nightly, 1.63.0]
        os: [ubuntu-24.04, macos-14, windows-2022]
        include:
          - toolchain: beta
            os: ubuntu-24.04
    runs-on: ${{ matrix.os }}
    timeout-minutes: 25
    env:
      TOOLCHAIN: ${{ matrix.toolchain }}
    steps:
      - uses: actions/checkout@v4
      # Remove `-Dwarnings` at the MSRV since lints may be different or buffier
      - name: Update RUSTFLAGS
        run: |
          set -eux
          [ "${{ matrix.toolchain }}" = "1.63.0" ] && echo 'RUSTFLAGS=' >> "$GITHUB_ENV" || true
          
      - name: Setup Rust toolchain
        run: ./ci/install-rust.sh

      # FIXME(ci): These `du` statements are temporary for debugging cache
      - name: Target size before restoring cache
        run: du -sh target | sort -k 2 || true
      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.os }}-${{ matrix.toolchain }}
      - name: Target size after restoring cache
        run: du -sh target | sort -k 2 || true

      - name: Execute build.sh
        run: ./ci/verify-build.sh
      - name: Target size after job completion
        run: du -sh target | sort -k 2

  test_tier1:
    name: Test tier1
    strategy:
      matrix:
        include:
          - target: i686-unknown-linux-gnu
            docker: true
            os: ubuntu-24.04
          - target: i686-unknown-linux-gnu
            docker: true
            os: ubuntu-24.04
            artifact-tag: offset-bits64
            env:
              RUST_LIBC_UNSTABLE_GNU_FILE_OFFSET_BITS: 64
          - target: i686-unknown-linux-gnu
            docker: true
            os: ubuntu-24.04
            artifact-tag: time-bits64
            env:
              RUST_LIBC_UNSTABLE_GNU_TIME_BITS: 64
          - target: x86_64-unknown-linux-gnu
            docker: true
            os: ubuntu-24.04
          - target: aarch64-apple-darwin
            os: macos-14
          - target: x86_64-pc-windows-gnu
            os: windows-2022
            env:
              ARCH_BITS: 64
              ARCH: x86_64
          - target: x86_64-pc-windows-msvc
            os: windows-2022
          # FIXME: It currently causes segfaults.
          #- target: i686-pc-windows-gnu
          #  env:
          #    ARCH_BITS: 32
          #    ARCH: i686
          - target: i686-pc-windows-msvc
            os: windows-2022
    runs-on: ${{ matrix.os }}
    timeout-minutes: 25
    env:
      TARGET: ${{ matrix.target }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup Rust toolchain
        run: ./ci/install-rust.sh
      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      - name: Add matrix env variables to the environment
        if: matrix.env
        run: |
          echo '${{ toJson(matrix.env) }}' |
            jq -r 'to_entries | map("\(.key)=\(.value|tostring)") | .[]' >>$GITHUB_ENV
        shell: bash

      - name: Run natively
        if: "!matrix.docker"
        run: ./ci/run.sh ${{ matrix.target }}
      - name: Run in Docker
        if: "matrix.docker"
        run: ./ci/run-docker.sh ${{ matrix.target }}

      - name: Create CI artifacts
        id: create_artifacts
        if: always()
        run: ./ci/create-artifacts.py
      - uses: actions/upload-artifact@v4
        if: always() && steps.create_artifacts.outcome == 'success'
        with:
          name: ${{ env.ARCHIVE_NAME }}-${{ matrix.target }}${{ matrix.artifact-tag && format('-{0}', matrix.artifact-tag) }}
          path: ${{ env.ARCHIVE_PATH }}
          retention-days: 5

  test_tier2:
    name: Test tier2
    needs: [test_tier1, style_check]
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: true
      max-parallel: 12
      matrix:
        target:
          # FIXME(sparc): this takes much longer to run than any other job, put
          # it first to make sure it gets a head start.
          - sparc64-unknown-linux-gnu
          - aarch64-linux-android
          - aarch64-unknown-linux-gnu
          - aarch64-unknown-linux-musl
          - arm-linux-androideabi
          - arm-unknown-linux-musleabihf
          # FIXME(#4297): Disabled due to spurious failueSome android jobs are disabled because of high rates of
          # - i686-linux-android
          - i686-unknown-linux-musl
          - loongarch64-unknown-linux-gnu
          - loongarch64-unknown-linux-musl
          - powerpc64-unknown-linux-gnu
          - powerpc64le-unknown-linux-gnu
          - riscv64gc-unknown-linux-gnu
          - s390x-unknown-linux-gnu
          - wasm32-unknown-emscripten
          - wasm32-wasip1
          - wasm32-wasip2
          - x86_64-linux-android
          # FIXME: Exec format error (os error 8)
          # - x86_64-unknown-linux-gnux32
          - x86_64-unknown-linux-musl
          # FIXME: It seems some items in `src/unix/mod.rs`
          # aren't defined on redox actually.
          # - x86_64-unknown-redox
        include:
          - target: arm-unknown-linux-gnueabihf
          - target: arm-unknown-linux-gnueabihf
            env:
              RUST_LIBC_UNSTABLE_GNU_FILE_OFFSET_BITS: 64
            artifact-tag: offset-bits64
          - target: arm-unknown-linux-gnueabihf
            env:
              RUST_LIBC_UNSTABLE_GNU_TIME_BITS: 64
            artifact-tag: time-bits64
          - target: aarch64-unknown-linux-musl
            env:
              RUST_LIBC_UNSTABLE_MUSL_V1_2_3: 1
          - target: arm-unknown-linux-musleabihf
            env:
              RUST_LIBC_UNSTABLE_MUSL_V1_2_3: 1
          - target: i686-unknown-linux-musl
            env:
              RUST_LIBC_UNSTABLE_MUSL_V1_2_3: 1
          - target: loongarch64-unknown-linux-musl
            env:
              RUST_LIBC_UNSTABLE_MUSL_V1_2_3: 1
          # FIXME(ppc): SIGILL running tests, see
          # https://github.com/rust-lang/libc/pull/4254#issuecomment-2636288713
          # - target: powerpc-unknown-linux-gnu
          # - target: powerpc-unknown-linux-gnu
          #   env:
          #     RUST_LIBC_UNSTABLE_GNU_FILE_OFFSET_BITS: 64
          #   artifact-tag: offset-bits64
          # - target: powerpc-unknown-linux-gnu
          #   env:
          #     RUST_LIBC_UNSTABLE_GNU_TIME_BITS: 64
          #   artifact-tag: time-bits64
    timeout-minutes: 25
    env:
      TARGET: ${{ matrix.target }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup Rust toolchain
        run: ./ci/install-rust.sh
      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      - name: Add matrix env variables to the environment
        if: matrix.env
        run: |
          echo '${{ toJson(matrix.env) }}' |
            jq -r 'to_entries | map("\(.key)=\(.value|tostring)") | .[]' >>$GITHUB_ENV
        shell: bash

      - name: Execute run-docker.sh
        run: ./ci/run-docker.sh ${{ matrix.target }}

      - name: Create CI artifacts
        id: create_artifacts
        if: always()
        run: ./ci/create-artifacts.py
      - uses: actions/upload-artifact@v4
        if: always() && steps.create_artifacts.outcome == 'success'
        with:
          name: ${{ env.ARCHIVE_NAME }}-${{ matrix.target }}${{ matrix.artifact-tag && format('-{0}', matrix.artifact-tag) }}
          path: ${{ env.ARCHIVE_PATH }}
          retention-days: 5

  test_tier2_vm:
    name: Test tier2 VM
    needs: [test_tier1, style_check]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
      matrix:
        target:
          - x86_64-pc-solaris
    timeout-minutes: 25
    steps:
      - uses: actions/checkout@v4
      - name: test on Solaris
        uses: vmactions/solaris-vm@v1.1.4
        with:
          release: "11.4-gcc"
          usesh: true
          mem: 4096
          copyback: false
          prepare: |
            set -x
            source <(curl -s https://raw.githubusercontent.com/psumbera/solaris-rust/refs/heads/main/sh.rust-web-install)
            rustc --version
            uname -a
          run: |
            export PATH=$HOME/.rust_solaris/bin:$PATH
            ./ci/run.sh ${{ matrix.target }}

  # One job that "summarizes" the success state of this pipeline. This can then be added to branch
  # protection, rather than having to add each job separately.
  success:
    name: success
    runs-on: ubuntu-24.04
    needs:
      - style_check
      - test_tier1
      - test_tier2
      - test_tier2_vm
      - verify_build
      - clippy
    # GitHub branch protection is exceedingly silly and treats "jobs skipped because a dependency
    # failed" as success. So we have to do some contortions to ensure the job fails if any of its
    # dependencies fails.
    if: always() # make sure this is never "skipped"
    steps:
      # Manually check the status of all dependencies. `if: failure()` does not work.
      - name: check if any dependency failed
        run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
