name: CI
on: [push, pull_request]

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

defaults:
  run:
    shell: bash

jobs:
  buildlibc:
    name: ${{ matrix.name }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # Test a number of operating systems and architectures to make sure
          # wasi-libc builds on these platforms by default.
          - name: Build on Linux x86_64
            os: ubuntu-24.04
            clang_version: 19
            upload: linux-x86_64-clang-19
          - name: Build on Linux aarch64
            os: ubuntu-24.04-arm
            clang_version: 19
            upload: linux-aarch64-clang-19
          - name: Build on macOS aarch64
            os: macos-15
            clang_version: 15.0.7
            llvm_asset_suffix: arm64-apple-darwin22.0
            upload: macos-clang-15
            args: -DBUILD_SHARED=OFF -DCMAKE_C_COMPILER_WORKS=ON
          - name: Build on Windows x86_64
            os: windows-2025
            clang_version: 20.1.8
            upload: windows-clang-20
          - name: Build on WASIp2 Windows x86_64
            os: windows-2025
            clang_version: 20.1.8
            args: -DTARGET_TRIPLE=wasm32-wasip2

          # Other versions of LLVM
          - name: Build with LLVM 11
            os: ubuntu-22.04
            clang_version: 11
            upload: linux-x86_64-clang-11
            args: -DSETJMP=OFF -DBUILD_SHARED=OFF -DENABLE_WERROR=OFF
          - name: Build with LLVM 18
            os: ubuntu-24.04
            clang_version: 18
            upload: linux-x86_64-clang-18

          # Test various combinations of target triples.
          #
          # Configuration here can happen through `args` which are all passed
          # to CMake. For now this only runs tests on Linux with Clang 16,
          # but that can be expanded as necessary in the future too.
          - name: Test wasm32-wasi
            os: ubuntu-24.04
            clang_version: 20
            test: true
            upload: wasm32-wasi
            args: -DTARGET_TRIPLE=wasm32-wasi

          - name: Test wasm32-wasip1
            os: ubuntu-24.04
            clang_version: 20
            test: true
            upload: wasm32-wasip1
            args: -DTARGET_TRIPLE=wasm32-wasip1

          - name: Test wasm32-wasip2
            os: ubuntu-24.04
            clang_version: 20
            test: true
            upload: wasm32-wasip2
            args: -DTARGET_TRIPLE=wasm32-wasip2

          - name: Test wasm32-wasip1-threads
            os: ubuntu-24.04
            clang_version: 20
            test: true
            upload: wasm32-wasip1-threads
            args: -DTARGET_TRIPLE=wasm32-wasip1-threads

          - name: Test wasm32-wasip1 in V8
            os: ubuntu-24.04
            clang_version: 20
            test: true
            test_with_v8: true
            args: -DTARGET_TRIPLE=wasm32-wasip1

          - name: Test wasm32-wasip1-threads in V8
            os: ubuntu-24.04
            clang_version: 20
            test: true
            test_with_v8: true
            args: -DTARGET_TRIPLE=wasm32-wasip1-threads

          - name: Test wasm32-wasi-simd
            os: ubuntu-24.04
            clang_version: 20
            test: true
            upload: wasm32-wasi-simd
            args: -DSIMD=ON -DCHECK_SYMBOLS=OFF

          - name: Test wasm32-wasip1 (debug)
            os: ubuntu-24.04
            clang_version: 20
            test: true
            args: -DCMAKE_BUILD_TYPE=Debug

          - name: Test wasm32-wasip2 (debug)
            os: ubuntu-24.04
            clang_version: 20
            test: true
            args: -DCMAKE_BUILD_TYPE=Debug -DTARGET_TRIPLE=wasm32-wasip2

          - name: Test wasm32-wasip1-threads (debug)
            os: ubuntu-24.04
            clang_version: 20
            test: true
            args: -DCMAKE_BUILD_TYPE=Debug -DTARGET_TRIPLE=wasm32-wasip1-threads

          - name: Test wasm32-wasip3
            os: ubuntu-24.04
            clang_version: 20
            test: true
            upload: wasm32-wasip3
            args: -DTARGET_TRIPLE=wasm32-wasip3

          - name: Test wasm32-wasip3 (debug)
            os: ubuntu-24.04
            clang_version: 20
            test: true
            args: -DCMAKE_BUILD_TYPE=Debug -DTARGET_TRIPLE=wasm32-wasip3

          - name: Test emmalloc
            os: ubuntu-24.04
            clang_version: 20
            test: true
            args: -DMALLOC=emmalloc

          - name: Test LTO
            os: ubuntu-24.04
            clang_version: 20
            test: true
            args: -DLTO=full -DTARGET_TRIPLE=wasm32-wasip2 -DCHECK_SYMBOLS=OFF

          - name: Test wasip2 on macOS
            os: macos-15
            clang_version: wasi-sdk-30
            test: true
            args: -DTARGET_TRIPLE=wasm32-wasip2 -DCMAKE_C_COMPILER_WORKS=ON

          - name: Test wasip2 on Windows
            os: windows-2025
            clang_version: 20.1.8
            test: true
            args: -DTARGET_TRIPLE=wasm32-wasip2

          - name: Test LLVM 22
            os: ubuntu-24.04
            clang_version: 22
            test: true
            args: -DTARGET_TRIPLE=wasm32-wasip2
    steps:
    - uses: actions/checkout@v6
      with:
        submodules: true

    - uses: ./.github/actions/setup
      with:
        clang_version: ${{ matrix.clang_version }}
        llvm_asset_suffix: ${{ matrix.llvm_asset_suffix }}

    - name: Install V8 dependencies
      if: matrix.test_with_v8
      run: |
        npm -C test/scripts/browser-test install
        npx -C test/scripts/browser-test playwright install chromium-headless-shell
        echo ENGINE="$PWD/test/scripts/browser-test/harness.mjs" >> $GITHUB_ENV
        echo CTEST_ARGS="--label-exclude v8fail" >> $GITHUB_ENV

    - name: Configure libc
      run: |
        cmake -G Ninja -S . -B build \
          -DCHECK_SYMBOLS=ON \
          ${{ matrix.args }} \
          -DCMAKE_LINK_DEPENDS_USE_LINKER=OFF

    - name: Configure testing
      if: matrix.test
      run: cmake -S . -B build -DENABLE_WERROR=ON -DBUILD_TESTS=ON -DENGINE=${{ env.ENGINE }}

    - name: Build
      run: ninja -C build -v

    - name: Test
      if: matrix.test
      run: ctest --test-dir build/test --output-on-failure -j4 $CTEST_ARGS

    - uses: actions/upload-artifact@v6
      if: matrix.upload
      with:
        name: ${{ format( 'sysroot-{0}.tgz', matrix.upload) }}
        path: build/sysroot


  # Disable the headerstest job for now, while WASI transitions from the
  # witx snapshots to wit proposals, and we have a few manual edits to the
  # generated header to make life easier for folks.
  headerstest:
    if: ${{ false }}  # Disable the headers test for now.
    name: wasi-headers test
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-15, windows-2025]
    steps:
    - uses: actions/checkout@v6
      with:
        submodules: true
    - name: Install Rust (rustup)
      run: rustup update stable --no-self-update && rustup default stable
      if: matrix.os != 'macos-15'
    - name: Install Rust (macos)
      run: |
        curl https://sh.rustup.rs | sh -s -- -y
        echo "$HOME/.cargo/bin" >> $GITHUB_PATH
      if: matrix.os == 'macos-15'
    - run: cargo fetch
      working-directory: tools/wasi-headers
    - run: cargo build
      working-directory: tools/wasi-headers
    - run: cargo test
      working-directory: tools/wasi-headers

  clang-format:
    name: Clang Format
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6
      with:
        submodules: true
    - run: cmake -S . -B build -DBUILD_TESTS=ON -G Ninja -DCMAKE_C_COMPILER=clang
    - run: ninja -C build format-check
    - run: cmake -S . -B build -DTARGET_TRIPLE=wasm32-wasip2
    - run: ninja -C build format-check
    - run: cmake -S . -B build -DTARGET_TRIPLE=wasm32-wasip1-threads
    - run: ninja -C build format-check

  rustfmt:
    name: Rustfmt
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6
      with:
        submodules: true
    - name: Install Rust
      run: rustup update stable && rustup default stable && rustup component add rustfmt
    - run: cargo fmt -- --check
      working-directory: tools/wasi-headers

  bindings:
    name: Ensure wit-bingen bindings are up-to-date
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6
      with:
        submodules: true
    - run: CC=clang cmake -S . -B build -G Ninja
    - run: ninja -C build bindings
    - run: git diff --exit-code

  # Currently wasi-libc's test suite is pretty bare-bones. To try to increase
  # test coverage build Python in CI as well with a fresh copy of wasi-libc.
  # This involves a bit of configuration to get all the stars to align, but in
  # the end this runs at least some Python tests against a from-source-built
  # copy of wasi-libc.
  python:
    name: Test Python ${{ matrix.name }}
    runs-on: ubuntu-24.04-arm
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: wasm32-wasip1
            args: -DTARGET_TRIPLE=wasm32-wasip1
          - name: emmalloc
            args: -DTARGET_TRIPLE=wasm32-wasip1 -DMALLOC=emmalloc
          - name: wasm32-wasip2
            args: -DTARGET_TRIPLE=wasm32-wasip2
          - name: wasm32-wasip3
            args: -DTARGET_TRIPLE=wasm32-wasip3
    steps:
    - uses: actions/checkout@v6
      with:
        submodules: true
    - run: |
        curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-32/wasi-sdk-32.0-arm64-linux.tar.gz -L | tar xzvf -
        echo "WASI_SDK_PATH=`pwd`/wasi-sdk-32.0-arm64-linux" >> $GITHUB_ENV
      if: runner.os == 'Linux'
      shell: bash
      working-directory: ${{ runner.tool_cache }}
    - name: Setup wasi-libc
      run: |
        cmake -S . -B build -G Ninja \
          ${{ matrix.args }} \
          -DCMAKE_LINK_DEPENDS_USE_LINKER=OFF \
          -DPYTHON_TESTS=ON \
          -DBUILD_TESTS=ON \
          -DCMAKE_C_COMPILER=$WASI_SDK_PATH/bin/clang
    - name: Build and test python
      run: ninja -C build -v python

  # This is a "join node" which depends on all prior workflows. The merge queue,
  # for example, gates on this to ensure that everything has executed
  # successfully. This enables decoupling the set of CI jobs specified in this
  # file with what should be gated on in the repository configuration. With a
  # join node here it means, for example, that adding a new CI job doesn't
  # require updating repository configuration and this can be updated directly
  # here.
  #
  # Note that this currently always runs to always report a status, even on
  # cancellation and even if dependency steps fail. Each dependency tries to
  # cancel the whole run if it fails, so if a test matrix entry fails, for
  # example, it cancels the build matrix entries too. This step then tries to
  # fail on cancellation to ensure that the dependency failures are propagated
  # correctly.
  ci-status:
    name: Record the result of this workflow
    runs-on: ubuntu-latest
    needs:
      - buildlibc
      - headerstest
      - clang-format
      - rustfmt
      - bindings
      - python
    if: always()
    steps:
    # Calculate the exit status of the whole CI workflow.
    # If all dependent jobs were successful, this exits with 0 (and the
    # outcome job continues successfully). If a some dependent job has
    # failed, this exits with 1.
    - name: calculate the correct exit status
      run: jq --exit-status 'all(.result == "success" or .result == "skipped")' <<< '${{ toJson(needs) }}'
