name: CI

on:
  push:
    branches:
      - main
  pull_request:
  merge_group:

env:
  RUSTFLAGS: -Dwarnings
  RUSTDOCFLAGS: -Dwarnings

defaults:
  run:
    shell: bash

jobs:
  check:
    name: Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: taiki-e/install-action@cargo-hack
      - uses: mkroening/rust-toolchain-toml@main
      - uses: Swatinem/rust-cache@v2
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - run: rustup target add x86_64-unknown-none
      - name: Check each feature
        run: cargo hack check --package hermit-kernel --each-feature --skip gem-net --no-dev-deps --target x86_64-unknown-none
        env:
          RUSTFLAGS:

  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mkroening/rust-toolchain-toml@main
      - run: rustup component add clippy
      - uses: Swatinem/rust-cache@v2
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
          workspaces: |
            .
            hermit-builtins
      - run: cargo xtask clippy

  format:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mkroening/rust-toolchain-toml@main
      - run: rustup component add rustfmt
      - run: cargo fmt -- --check

  check-docs:
    name: Check docs
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mkroening/rust-toolchain-toml@main
      - run: rustup target add x86_64-unknown-none aarch64-unknown-none-softfloat
      - uses: Swatinem/rust-cache@v2
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: Check docs
        run: |
          cargo doc --package hermit-kernel --no-deps --document-private-items --target x86_64-unknown-none
          cargo doc --package hermit-kernel --no-deps --document-private-items --target aarch64-unknown-none-softfloat --no-default-features

  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mkroening/rust-toolchain-toml@main
      - uses: Swatinem/rust-cache@v2
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
          workspaces: |
            .
            hermit-builtins
      - name: Build minimal kernel
        run: |
          cargo xtask build --arch x86_64 --no-default-features
          cargo xtask build --arch aarch64 --no-default-features

  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install QEMU
        run: |
          sudo apt-get update
          sudo apt-get install qemu-system-x86
      - uses: mkroening/rust-toolchain-toml@main
      - uses: Swatinem/rust-cache@v2
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
          workspaces: |
            .
            hermit-builtins
      - name: Unit tests
        run: cargo test --lib
        env:
          RUSTFLAGS: -Awarnings
      - name: Download loader
        uses: dsaltares/fetch-gh-release-asset@master
        with:
          repo: hermit-os/loader
          file: hermit-loader-x86_64
      - run: rustup target add x86_64-unknown-none
      - name: Integration tests
        run: cargo test --tests --no-fail-fast --target x86_64-unknown-none -- --bootloader_path=hermit-loader-x86_64
        env:
          RUSTFLAGS:

  run-hermit:
    name: Run
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: kernel
    strategy:
      matrix:
        arch: [x86_64, aarch64, riscv64]
        package: [rusty_demo, httpd, testudp, hello_world]
        netdev: [none, virtio-net-pci, rtl8139]
        profile: [dev, release]
        smp: [1, 4]
        exclude:
          - arch: riscv64
            package: httpd
          - arch: riscv64
            package: testudp
          # microvm (Firecracker) test does not run on aarch64 or riscv64
          - package: hello_world
            arch: aarch64
          - package: hello_world
            arch: riscv64
          # https://github.com/hermit-os/kernel/issues/995
          - package: hello_world
            smp: 4
          # https://github.com/hermit-os/kernel/issues/737
          - arch: aarch64
            smp: 4
          # rtl8139 support does not build on aarch64
          - arch: aarch64
            netdev: rtl8139
          # Following packages do not depend on the netdev
          - package: rusty_demo
            netdev: virtio-net-pci
          - package: rusty_demo
            netdev: rtl8139
          - package: hello_world
            netdev: virtio-net-pci
          - package: hello_world
            netdev: rtl8139
          # Following packages require a netdev
          - package: httpd
            netdev: none
          - package: testudp
            netdev: none
        include:
          - arch: x86_64
            packages: qemu-system-x86 libcap-ng-dev libseccomp-dev socat
          - arch: aarch64
            packages: qemu-system-aarch64
          - arch: riscv64
            packages: qemu-system-misc
            flags: --no-default-features

          - arch: x86_64
            package: rusty_demo
            flags: --features pci-ids --virtiofsd
          - arch: aarch64
            package: rusty_demo
            flags: --features pci-ids
          - package: httpd
            flags: --features ci,dhcpv4
          - package: testudp
            flags: --features udp,dhcpv4
          - package: hello_world
            flags: --no-default-features --microvm

    steps:
      - name: Checkout hermit-rs
        uses: actions/checkout@v4
        with:
          repository: hermit-os/hermit-rs
          submodules: true
      - name: Remove hermit-kernel submodule
        run: git rm -r kernel
        working-directory: .
      - name: Checkout hermit-kernel
        uses: actions/checkout@v4
        with:
          path: kernel
      - name: Install QEMU
        run: |
          sudo apt-get update
          sudo apt-get install ${{ matrix.packages }}
      - uses: mkroening/rust-toolchain-toml@main
      - uses: mkroening/rust-toolchain-toml@main
        with:
          toolchain-file: 'kernel/rust-toolchain.toml'
      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.arch }}-${{ matrix.profile }}
          save-if: ${{ github.ref == 'refs/heads/main' && matrix.package == 'rusty_demo' }}
          workspaces: |
            .
            kernel
            kernel/hermit-builtins
      - name: Download loader
        uses: dsaltares/fetch-gh-release-asset@master
        with:
          repo: hermit-os/loader
          file: hermit-loader-${{ matrix.arch }}
      - name: Install virtiofsd
        if: ${{ matrix.arch == 'x86_64' && matrix.package == 'rusty_demo' }}
        run: cargo install --locked virtiofsd
      - name: Run VM
        # cargo accepts multiple 'features' flags
        # The '… && … || …' expression is like a ternary operator
        run: >
              cargo xtask ci qemu
              --arch ${{ matrix.arch }}
              --package ${{ matrix.package }}
              --smp ${{ matrix.smp }}
              ${{ matrix.flags }}
              ${{ matrix.netdev == 'rtl8139' && '--features rtl8139' || '' }}
              ${{ matrix.netdev != 'none' && format('--netdev {0}', matrix.netdev) || '' }}
              --profile ${{ matrix.profile }}

  run-x86_64-kvm:
    name: Run Hermit for Rust (x86_64 + kvm)
    runs-on: [self-hosted]
    defaults:
      run:
        working-directory: kernel
    steps:
    - name: Checkout hermit-rs
      uses: actions/checkout@v4
      with:
        repository: hermit-os/hermit-rs
        submodules: true
    - name: Remove hermit-kernel submodule
      run: git rm -r kernel
      working-directory: .
    - name: Checkout hermit-kernel
      uses: actions/checkout@v4
      with:
        path: kernel
    - name: Install QEMU
      run: |
        sudo apt-get update
        sudo apt-get install -y --no-install-recommends qemu-system-x86
    - name: Check KVM availability
      shell: bash
      run: |
          lscpu
          kvm-ok
    - name: Download loader (x86_64)
      uses: dsaltares/fetch-gh-release-asset@master
      with:
        file: hermit-loader-x86_64
        repo: hermit-os/loader
    - name: Download loader (x86_64-fc)
      uses: dsaltares/fetch-gh-release-asset@master
      with:
        file: hermit-loader-x86_64-fc
        repo: hermit-os/loader
    - name: Install firecracker
      run: |
        # https://github.com/firecracker-microvm/firecracker/blob/v1.5.1/docs/getting-started.md#getting-a-firecracker-binary
        ARCH="$(uname -m)"
        release_url="https://github.com/firecracker-microvm/firecracker/releases"
        latest=$(basename $(curl -fsSLI -o /dev/null -w  %{url_effective} ${release_url}/latest))
        curl -L ${release_url}/download/${latest}/firecracker-${latest}-${ARCH}.tgz \
        | tar -xz
        
        # Rename the binary to "firecracker"
        mv release-${latest}-$(uname -m)/firecracker-${latest}-${ARCH} firecracker
        echo "$PWD" >> $GITHUB_PATH

        ./firecracker --version
    - uses: dtolnay/rust-toolchain@stable
    - run: cargo install uhyve
    - uses: mkroening/rust-toolchain-toml@main
    - uses: mkroening/rust-toolchain-toml@main
      with:
        toolchain-file: 'kernel/rust-toolchain.toml'
    - uses: Swatinem/rust-cache@v2
      with:
        save-if: ${{ github.ref == 'refs/heads/main' }}
        workspaces: |
          .
          kernel
          kernel/hermit-builtins
    - name: rusty_demo on Uhyve
      run: cargo xtask ci uhyve --arch x86_64 --package rusty_demo
    - name: rusty_demo on Uhyve (release)
      run: cargo xtask ci uhyve --arch x86_64 --package rusty_demo --release
    - name: rusty_demo on QEMU (with KVM)
      run: cargo xtask ci qemu --arch x86_64 --package rusty_demo --accel
    - name: rusty_demo on QEMU (with KVM, release)
      run: cargo xtask ci qemu --arch x86_64 --package rusty_demo --accel --release
    - name: hello_world on Firecracker
      run: cargo xtask ci firecracker --arch x86_64 --package hello_world --no-default-features
