From 1b0d691b5783b99fdd17ddfc72eceedd7301de26 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 10 Dec 2025 18:57:19 +0000 Subject: [PATCH] Make PR Validation job more paralleslised Signed-off-by: Simon Davies --- .github/workflows/Benchmarks.yml | 15 +- .github/workflows/CargoPublish.yml | 1 - .github/workflows/CreateRelease.yml | 19 +- .github/workflows/Fuzzing.yml | 1 - .github/workflows/RustNightly.yml | 111 ++++++++- .github/workflows/ValidatePullRequest.yml | 82 ++++++- .github/workflows/dep_benchmarks.yml | 84 +++++++ .../workflows/dep_build_guest_binaries.yml | 58 ----- .github/workflows/dep_build_guests.yml | 113 +++++++++ .github/workflows/dep_build_test.yml | 122 ++++++++++ .github/workflows/dep_code_checks.yml | 155 ++++++++++++ .github/workflows/dep_fuzzing.yml | 16 +- .github/workflows/dep_run_examples.yml | 88 +++++++ .github/workflows/dep_rust.yml | 221 ------------------ Justfile | 20 +- src/hyperlight_host/src/mem/shared_mem.rs | 27 ++- 16 files changed, 784 insertions(+), 349 deletions(-) create mode 100644 .github/workflows/dep_benchmarks.yml delete mode 100644 .github/workflows/dep_build_guest_binaries.yml create mode 100644 .github/workflows/dep_build_guests.yml create mode 100644 .github/workflows/dep_build_test.yml create mode 100644 .github/workflows/dep_code_checks.yml create mode 100644 .github/workflows/dep_run_examples.yml delete mode 100644 .github/workflows/dep_rust.yml diff --git a/.github/workflows/Benchmarks.yml b/.github/workflows/Benchmarks.yml index 8b80fbf9a..ae3bee1d1 100644 --- a/.github/workflows/Benchmarks.yml +++ b/.github/workflows/Benchmarks.yml @@ -6,7 +6,6 @@ on: workflow_call: # This is called from CreateRelease.yml permissions: - id-token: write contents: read jobs: @@ -40,20 +39,14 @@ jobs: - name: Download Rust Guest Binaries uses: actions/download-artifact@v6 with: - name: rust-guest-binaries-release - path: ./downloaded-rust-guest-binaries-release + name: rust-guests-release + path: src/tests/rust_guests/bin/release/ - name: Download C Guest Binaries uses: actions/download-artifact@v6 with: - name: c-guest-binaries-release - path: ./downloaded-c-guest-binaries-release - - - name: Copy Guest Binaries - run: | - cp ./downloaded-rust-guest-binaries-release/simpleguest ./src/tests/rust_guests/bin/release/simpleguest - cp ./downloaded-rust-guest-binaries-release/dummyguest ./src/tests/rust_guests/bin/release/dummyguest - cp ./downloaded-c-guest-binaries-release/simpleguest ./src/tests/c_guests/bin/release/simpleguest + name: c-guests-release + path: src/tests/c_guests/bin/release/ ### Benchmarks ### - name: Fetch tags diff --git a/.github/workflows/CargoPublish.yml b/.github/workflows/CargoPublish.yml index 0175a4966..d7a28e74c 100644 --- a/.github/workflows/CargoPublish.yml +++ b/.github/workflows/CargoPublish.yml @@ -18,7 +18,6 @@ on: permissions: contents: read - id-token: write jobs: publish-hyperlight-packages: diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index 0e03b9427..d6ec48911 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -9,7 +9,6 @@ on: permissions: contents: write - id-token: write issues: read jobs: @@ -64,17 +63,18 @@ jobs: - name: Build Release run: cargo build --verbose --release - build-guest-binaries: - uses: ./.github/workflows/dep_build_guest_binaries.yml + build-guests: + uses: ./.github/workflows/dep_build_guests.yml secrets: inherit needs: [release-blocker-check] + with: + config: release benchmarks: - needs: [build-guest-binaries] + needs: [build-guests] uses: ./.github/workflows/Benchmarks.yml secrets: inherit permissions: - id-token: write contents: read cargo-publish: @@ -84,7 +84,6 @@ jobs: dry_run: false secrets: inherit permissions: - id-token: write contents: read publish: @@ -140,9 +139,11 @@ jobs: - name: Archive benchmarks run: | - # windows + # windows (hyperv = Server 2022, hyperv-ws2025 = Server 2025) tar -zcvf benchmarks_Windows_hyperv_amd.tar.gz benchmarks_Windows_hyperv_amd tar -zcvf benchmarks_Windows_hyperv_intel.tar.gz benchmarks_Windows_hyperv_intel + tar -zcvf benchmarks_Windows_hyperv-ws2025_amd.tar.gz benchmarks_Windows_hyperv-ws2025_amd + tar -zcvf benchmarks_Windows_hyperv-ws2025_intel.tar.gz benchmarks_Windows_hyperv-ws2025_intel # kvm tar -zcvf benchmarks_Linux_kvm_amd.tar.gz benchmarks_Linux_kvm_amd tar -zcvf benchmarks_Linux_kvm_intel.tar.gz benchmarks_Linux_kvm_intel @@ -168,6 +169,8 @@ jobs: gh release create v${{ env.HYPERLIGHT_VERSION }} -t "Release v${{ env.HYPERLIGHT_VERSION }}" --notes-file RELEASE_NOTES.md \ benchmarks_Windows_hyperv_amd.tar.gz \ benchmarks_Windows_hyperv_intel.tar.gz \ + benchmarks_Windows_hyperv-ws2025_amd.tar.gz \ + benchmarks_Windows_hyperv-ws2025_intel.tar.gz \ benchmarks_Linux_kvm_amd.tar.gz \ benchmarks_Linux_kvm_intel.tar.gz \ benchmarks_Linux_mshv3_amd.tar.gz \ @@ -184,6 +187,8 @@ jobs: gh release create dev-latest -t "Latest prerelease from main branch" --notes-file RELEASE_NOTES.md --latest=false -p \ benchmarks_Windows_hyperv_amd.tar.gz \ benchmarks_Windows_hyperv_intel.tar.gz \ + benchmarks_Windows_hyperv-ws2025_amd.tar.gz \ + benchmarks_Windows_hyperv-ws2025_intel.tar.gz \ benchmarks_Linux_kvm_amd.tar.gz \ benchmarks_Linux_kvm_intel.tar.gz \ benchmarks_Linux_mshv3_amd.tar.gz \ diff --git a/.github/workflows/Fuzzing.yml b/.github/workflows/Fuzzing.yml index a7fedc1ab..d4599bc79 100644 --- a/.github/workflows/Fuzzing.yml +++ b/.github/workflows/Fuzzing.yml @@ -6,7 +6,6 @@ on: workflow_dispatch: # Allow manual triggering permissions: - id-token: write contents: read jobs: diff --git a/.github/workflows/RustNightly.yml b/.github/workflows/RustNightly.yml index 01c592f8c..6f36caca7 100644 --- a/.github/workflows/RustNightly.yml +++ b/.github/workflows/RustNightly.yml @@ -9,24 +9,119 @@ on: - cron: '0 0 */2 * *' permissions: - id-token: write contents: read +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +defaults: + run: + shell: bash + jobs: + # Nightly musl cross-compilation builds + # This is a self-contained job since musl builds are a special case + # that require TARGET_TRIPLE for cross-compilation musl: + timeout-minutes: 60 strategy: fail-fast: true matrix: hypervisor: [kvm, mshv3] cpu: [amd, intel] config: [debug, release] - uses: ./.github/workflows/dep_rust.yml - secrets: inherit - with: - hypervisor: ${{ matrix.hypervisor }} - cpu: ${{ matrix.cpu }} - config: ${{ matrix.config }} - target_triple: x86_64-unknown-linux-musl + runs-on: ${{ fromJson( + format('["self-hosted", "Linux", "X64", "1ES.Pool=hld-{0}-{1}"]', + matrix.hypervisor == 'mshv3' && 'azlinux3-mshv' || matrix.hypervisor, + matrix.cpu)) }} + env: + TARGET_TRIPLE: x86_64-unknown-linux-musl + steps: + - uses: actions/checkout@v6 + + - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix cargo home permissions + run: | + sudo chown -R $(id -u):$(id -g) /opt/cargo || true + + # cargo-hyperlight builds a custom sysroot for x86_64-hyperlight-none target. + # rust-cache cleans "anything not a dependency" from target dirs, removing the sysroot. + # We cache sysroot separately to avoid rebuilding it (~10s) on every run. + - name: Sysroot cache + uses: actions/cache@v4 + with: + path: | + src/tests/rust_guests/simpleguest/target/sysroot + src/tests/rust_guests/dummyguest/target/sysroot + src/tests/rust_guests/witguest/target/sysroot + key: sysroot-linux-${{ matrix.config }}-${{ hashFiles('rust-toolchain.toml') }} + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: "nightly-${{ matrix.config }}" + cache-on-failure: "true" + workspaces: | + . -> target + src/tests/rust_guests/simpleguest -> target + src/tests/rust_guests/dummyguest -> target + src/tests/rust_guests/witguest -> target + + - name: Build and move Rust guests + run: | + just build-rust-guests ${{ matrix.config }} + just move-rust-guests ${{ matrix.config }} + + - name: Build C guests + run: | + just build-c-guests ${{ matrix.config }} + just move-c-guests ${{ matrix.config }} + + - name: Build + run: just build ${{ matrix.config }} + + - name: Run Miri tests + run: just miri-tests + + - name: Run Rust tests + run: | + # with default features + just test ${{ matrix.config }} + + # with only one driver enabled + just test ${{ matrix.config }} ${{ matrix.hypervisor == 'mshv3' && 'mshv3' || 'kvm' }} + + # make sure certain cargo features compile + just check + + # without any features + just test-compilation-no-default-features ${{ matrix.config }} + + - name: Run Rust examples + env: + RUST_LOG: debug + run: just run-rust-examples-linux ${{ matrix.config }} + + - name: Run Rust Gdb tests + env: + RUST_LOG: debug + run: just test-rust-gdb-debugging ${{ matrix.config }} + + - name: Run Rust Crashdump tests + env: + RUST_LOG: debug + run: just test-rust-crashdump ${{ matrix.config }} + + - name: Run Rust Tracing tests + env: + RUST_LOG: debug + run: just test-rust-tracing ${{ matrix.config }} notify-failure: runs-on: ubuntu-latest diff --git a/.github/workflows/ValidatePullRequest.yml b/.github/workflows/ValidatePullRequest.yml index a9f38ce7f..9fc686bba 100644 --- a/.github/workflows/ValidatePullRequest.yml +++ b/.github/workflows/ValidatePullRequest.yml @@ -14,7 +14,6 @@ concurrency: cancel-in-progress: true permissions: - id-token: write contents: write jobs: @@ -41,31 +40,91 @@ jobs: return all_file_count === docs_file_count; result-encoding: string - rust: + # Build guests once, upload as artifacts for other jobs to download + build-guests: + needs: docs-pr + strategy: + fail-fast: true + matrix: + config: [debug, release] + uses: ./.github/workflows/dep_build_guests.yml + secrets: inherit + with: + docs_only: ${{ needs.docs-pr.outputs.docs-only }} + config: ${{ matrix.config }} + + # Code checks (fmt, clippy, MSRV) - runs in parallel with build-guests + code-checks: + needs: docs-pr + uses: ./.github/workflows/dep_code_checks.yml + secrets: inherit + with: + docs_only: ${{ needs.docs-pr.outputs.docs-only }} + + # Build and test - needs guest artifacts + build-test: needs: - docs-pr + - build-guests strategy: fail-fast: true matrix: hypervisor: [hyperv, 'hyperv-ws2025', mshv3, kvm] cpu: [amd, intel] config: [debug, release] - uses: ./.github/workflows/dep_rust.yml + uses: ./.github/workflows/dep_build_test.yml secrets: inherit with: - docs_only: ${{needs.docs-pr.outputs.docs-only}} + docs_only: ${{ needs.docs-pr.outputs.docs-only }} hypervisor: ${{ matrix.hypervisor }} cpu: ${{ matrix.cpu }} config: ${{ matrix.config }} + # Run examples - needs guest artifacts, runs in parallel with build-test + run-examples: + needs: + - docs-pr + - build-guests + strategy: + fail-fast: true + matrix: + hypervisor: [hyperv, 'hyperv-ws2025', mshv3, kvm] + cpu: [amd, intel] + config: [debug, release] + uses: ./.github/workflows/dep_run_examples.yml + secrets: inherit + with: + docs_only: ${{ needs.docs-pr.outputs.docs-only }} + hypervisor: ${{ matrix.hypervisor }} + cpu: ${{ matrix.cpu }} + config: ${{ matrix.config }} + + # Run benchmarks - release only, needs guest artifacts, runs in parallel with build-test + benchmarks: + needs: + - docs-pr + - build-guests + strategy: + fail-fast: true + matrix: + hypervisor: [hyperv, 'hyperv-ws2025', mshv3, kvm] + cpu: [amd, intel] + uses: ./.github/workflows/dep_benchmarks.yml + secrets: inherit + with: + docs_only: ${{ needs.docs-pr.outputs.docs-only }} + hypervisor: ${{ matrix.hypervisor }} + cpu: ${{ matrix.cpu }} + fuzzing: needs: - docs-pr + - build-guests uses: ./.github/workflows/dep_fuzzing.yml with: targets: '["fuzz_host_print", "fuzz_guest_call", "fuzz_host_call", "fuzz_guest_estimate_trace_event", "fuzz_guest_trace"]' # Pass as a JSON array max_total_time: 300 # 5 minutes in seconds - docs_only: ${{needs.docs-pr.outputs.docs-only}} + docs_only: ${{ needs.docs-pr.outputs.docs-only }} secrets: inherit spelling: @@ -85,21 +144,20 @@ jobs: run: ./dev/check-license-headers.sh # Gate PR merges on this specific "join-job" which requires all other - # jobs to run first. We need this job since we cannot gate on particular jobs - # in the workflow, since they can sometimes be skipped (e.g. if the PR only touches docs). - # This step fixes this issue by always running. + # jobs to run first. report-ci-status: needs: - docs-pr - - rust + - build-guests + - code-checks + - build-test + - run-examples + - benchmarks - fuzzing - spelling - license-headers if: always() runs-on: ubuntu-latest 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) }}' diff --git a/.github/workflows/dep_benchmarks.yml b/.github/workflows/dep_benchmarks.yml new file mode 100644 index 000000000..c923f6ab6 --- /dev/null +++ b/.github/workflows/dep_benchmarks.yml @@ -0,0 +1,84 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Run Benchmarks + +on: + workflow_call: + inputs: + docs_only: + description: Skip building if docs only + required: false + type: string + default: "false" + hypervisor: + description: Hypervisor for this run (passed from caller matrix) + required: true + type: string + cpu: + description: CPU architecture for the build (passed from caller matrix) + required: true + type: string + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + run-benchmarks: + if: ${{ inputs.docs_only == 'false' }} + timeout-minutes: 30 + runs-on: ${{ fromJson( + format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-{2}"]', + (inputs.hypervisor == 'hyperv' || inputs.hypervisor == 'hyperv-ws2025') && 'Windows' || 'Linux', + inputs.hypervisor == 'hyperv' && 'win2022' || inputs.hypervisor == 'hyperv-ws2025' && 'win2025' || inputs.hypervisor == 'mshv3' && 'azlinux3-mshv' || inputs.hypervisor, + inputs.cpu)) }} + steps: + - uses: actions/checkout@v6 + + - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix cargo home permissions + if: runner.os == 'Linux' + run: | + sudo chown -R $(id -u):$(id -g) /opt/cargo || true + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: "${{ runner.os }}-release" + save-if: "false" + + - name: Download Rust guests + uses: actions/download-artifact@v6 + with: + name: rust-guests-release + path: src/tests/rust_guests/bin/release/ + + - name: Download C guests + uses: actions/download-artifact@v6 + with: + name: c-guests-release + path: src/tests/c_guests/bin/release/ + + - name: Build + run: just build release + + - name: Download benchmarks from "latest" + run: just bench-download ${{ runner.os }} ${{ inputs.hypervisor }} ${{ inputs.cpu }} dev-latest # compare to prerelease + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + + - name: Run benchmarks + run: just bench-ci main diff --git a/.github/workflows/dep_build_guest_binaries.yml b/.github/workflows/dep_build_guest_binaries.yml deleted file mode 100644 index c21c06b81..000000000 --- a/.github/workflows/dep_build_guest_binaries.yml +++ /dev/null @@ -1,58 +0,0 @@ -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json - -name: Build Guest Binaries - -on: - workflow_call: - -env: - CARGO_TERM_COLOR: always - -permissions: - id-token: write - contents: read - -jobs: - # this job has no dependencies - build-guest-binaries: - runs-on: [self-hosted, linux, x64, "1ES.Pool=hld-kvm-amd"] - strategy: - fail-fast: true - matrix: - build: [hyperlight-kvm-debug, hyperlight-kvm-release] - include: - - build: hyperlight-kvm-debug - os: hyperlight-kvm - config: debug - - build: hyperlight-kvm-release - os: hyperlight-kvm - config: release - - steps: - - uses: actions/checkout@v6 - - - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 - with: - rust-toolchain: "1.89" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and move Rust and C guests - run: just guests - - - name: Upload Rust Guest Artifacts - uses: actions/upload-artifact@v5 - with: - name: rust-guest-binaries-${{ matrix.config }} - path: | - src\tests\rust_guests\bin\${{ matrix.config }}\dummyguest - src\tests\rust_guests\bin\${{ matrix.config }}\simpleguest - if-no-files-found: error - - - name: Upload C Guest Artifacts - uses: actions/upload-artifact@v5 - with: - name: c-guest-binaries-${{ matrix.config }} - path: | - src\tests\c_guests\bin\${{ matrix.config }}\simpleguest - if-no-files-found: error diff --git a/.github/workflows/dep_build_guests.yml b/.github/workflows/dep_build_guests.yml new file mode 100644 index 000000000..d621be19d --- /dev/null +++ b/.github/workflows/dep_build_guests.yml @@ -0,0 +1,113 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Build Guests + +on: + workflow_call: + inputs: + docs_only: + description: Skip building if docs only + required: false + type: string + default: "false" + config: + description: Build configuration (debug or release) + required: true + type: string + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + build-guests: + if: ${{ inputs.docs_only == 'false' }} + timeout-minutes: 15 + runs-on: [self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd"] + steps: + - uses: actions/checkout@v6 + + - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix cargo home permissions + if: runner.os == 'Linux' + run: | + sudo chown -R $(id -u):$(id -g) /opt/cargo || true + + # cargo-hyperlight builds a custom sysroot for x86_64-hyperlight-none target. + # rust-cache cleans "anything not a dependency" from target dirs, removing the sysroot. + # We cache sysroot separately to avoid rebuilding it (~10s) on every run. + - name: Sysroot cache + uses: actions/cache@v4 + with: + path: | + src/tests/rust_guests/simpleguest/target/sysroot + src/tests/rust_guests/dummyguest/target/sysroot + src/tests/rust_guests/witguest/target/sysroot + key: sysroot-linux-${{ inputs.config }}-${{ hashFiles('rust-toolchain.toml') }} + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: "guests-${{ inputs.config }}" + cache-on-failure: "true" + workspaces: | + . -> target + src/tests/rust_guests/simpleguest -> target + src/tests/rust_guests/dummyguest -> target + src/tests/rust_guests/witguest -> target + + - name: Build Rust guests + run: | + just build-rust-guests ${{ inputs.config }} + just move-rust-guests ${{ inputs.config }} + + - name: Build C guests + run: | + just build-c-guests ${{ inputs.config }} + just move-c-guests ${{ inputs.config }} + + - name: Upload Rust guests + uses: actions/upload-artifact@v5 + with: + name: rust-guests-${{ inputs.config }} + path: | + src/tests/rust_guests/bin/${{ inputs.config }}/ + retention-days: 1 + if-no-files-found: error + + - name: Upload C guests + uses: actions/upload-artifact@v5 + with: + name: c-guests-${{ inputs.config }} + path: src/tests/c_guests/bin/${{ inputs.config }}/ + retention-days: 1 + if-no-files-found: error + + - name: Upload interface.wasm + if: inputs.config == 'debug' + uses: actions/upload-artifact@v5 + with: + name: interface-wasm + path: src/tests/rust_guests/witguest/interface.wasm + retention-days: 1 + if-no-files-found: error + + - name: Upload twoworlds.wasm + if: inputs.config == 'debug' + uses: actions/upload-artifact@v5 + with: + name: twoworlds-wasm + path: src/tests/rust_guests/witguest/twoworlds.wasm + retention-days: 1 + if-no-files-found: error diff --git a/.github/workflows/dep_build_test.yml b/.github/workflows/dep_build_test.yml new file mode 100644 index 000000000..e79d6e06e --- /dev/null +++ b/.github/workflows/dep_build_test.yml @@ -0,0 +1,122 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Build and Test + +on: + workflow_call: + inputs: + docs_only: + description: Skip building if docs only + required: false + type: string + default: "false" + hypervisor: + description: Hypervisor for this run (passed from caller matrix) + required: true + type: string + config: + description: Build configuration for this run (passed from caller matrix) + required: true + type: string + cpu: + description: CPU architecture for the build (passed from caller matrix) + required: true + type: string + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + build-and-test: + if: ${{ inputs.docs_only == 'false' }} + timeout-minutes: 45 + runs-on: ${{ fromJson( + format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-{2}"]', + (inputs.hypervisor == 'hyperv' || inputs.hypervisor == 'hyperv-ws2025') && 'Windows' || 'Linux', + inputs.hypervisor == 'hyperv' && 'win2022' || inputs.hypervisor == 'hyperv-ws2025' && 'win2025' || inputs.hypervisor == 'mshv3' && 'azlinux3-mshv' || inputs.hypervisor, + inputs.cpu)) }} + steps: + - uses: actions/checkout@v6 + + - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix cargo home permissions + if: runner.os == 'Linux' + run: | + sudo chown -R $(id -u):$(id -g) /opt/cargo || true + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: "${{ runner.os }}-${{ inputs.config }}" + cache-on-failure: "true" + + - name: Download Rust guests + uses: actions/download-artifact@v6 + with: + name: rust-guests-${{ inputs.config }} + path: src/tests/rust_guests/bin/${{ inputs.config }}/ + + - name: Download C guests + uses: actions/download-artifact@v6 + with: + name: c-guests-${{ inputs.config }} + path: src/tests/c_guests/bin/${{ inputs.config }}/ + + - name: Download interface.wasm + uses: actions/download-artifact@v6 + with: + name: interface-wasm + path: src/tests/rust_guests/witguest/ + + - name: Download twoworlds.wasm + uses: actions/download-artifact@v6 + with: + name: twoworlds-wasm + path: src/tests/rust_guests/witguest/ + + - name: Build + run: just build ${{ inputs.config }} + + - name: Run Miri tests + if: runner.os == 'Linux' + run: just miri-tests + + - name: Run Rust tests + run: | + # with default features + just test ${{ inputs.config }} + + - name: Run Rust tests with single driver + if: runner.os == 'Linux' + run: | + # with only one driver enabled (kvm/mshv3 features are unix-only, no-op on Windows) + just test ${{ inputs.config }} ${{ inputs.hypervisor == 'mshv3' && 'mshv3' || 'kvm' }} + + - name: Run Rust Gdb tests + env: + RUST_LOG: debug + run: just test-rust-gdb-debugging ${{ inputs.config }} + + - name: Run Rust Crashdump tests + env: + RUST_LOG: debug + run: just test-rust-crashdump ${{ inputs.config }} + + - name: Run Rust Tracing tests + if: runner.os == 'Linux' + env: + RUST_LOG: debug + run: just test-rust-tracing ${{ inputs.config }} diff --git a/.github/workflows/dep_code_checks.yml b/.github/workflows/dep_code_checks.yml new file mode 100644 index 000000000..db65e91b6 --- /dev/null +++ b/.github/workflows/dep_code_checks.yml @@ -0,0 +1,155 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Code Checks + +on: + workflow_call: + inputs: + docs_only: + description: Skip building if docs only + required: false + type: string + default: "false" + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + linux-checks: + if: ${{ inputs.docs_only == 'false' }} + timeout-minutes: 30 + runs-on: ["self-hosted", "Linux", "X64", "1ES.Pool=hld-kvm-amd"] + steps: + - uses: actions/checkout@v6 + + - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix cargo home permissions + run: | + sudo chown -R $(id -u):$(id -g) /opt/cargo || true + + # cargo-hyperlight builds a custom sysroot for x86_64-hyperlight-none target. + # rust-cache cleans "anything not a dependency" from target dirs, removing the sysroot. + # We cache sysroot separately to avoid rebuilding it (~10s) on every run. + - name: Sysroot cache + uses: actions/cache@v4 + with: + path: | + src/tests/rust_guests/simpleguest/target/sysroot + src/tests/rust_guests/dummyguest/target/sysroot + src/tests/rust_guests/witguest/target/sysroot + key: sysroot-linux-${{ hashFiles('rust-toolchain.toml') }} + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: "code-checks-linux" + cache-on-failure: "true" + workspaces: | + . -> target + src/tests/rust_guests/simpleguest -> target + src/tests/rust_guests/dummyguest -> target + src/tests/rust_guests/witguest -> target + + - name: Ensure up-to-date Cargo.lock + run: cargo fetch --locked + + - name: fmt + run: just fmt-check + + - name: clippy exhaustive check (debug) + run: just clippy-exhaustive debug + + - name: clippy exhaustive check (release) + run: just clippy-exhaustive release + + - name: Verify MSRV + run: ./dev/verify-msrv.sh hyperlight-host hyperlight-guest hyperlight-guest-bin hyperlight-common + + - name: Check hyperlight-guest builds for 32-bit (Nanvix compatibility) + run: | + rustup target add i686-unknown-linux-gnu + just check-guest-i686 debug + + - name: Check cargo features compile + run: just check + + - name: Check compilation with no default features + run: | + just test-compilation-no-default-features debug + just test-compilation-no-default-features release + + windows-checks: + if: ${{ inputs.docs_only == 'false' }} + timeout-minutes: 30 + runs-on: ["self-hosted", "Windows", "X64", "1ES.Pool=hld-win2025-amd"] + steps: + - uses: actions/checkout@v6 + + - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # cargo-hyperlight builds a custom sysroot for x86_64-hyperlight-none target. + # rust-cache cleans "anything not a dependency" from target dirs, removing the sysroot. + # We cache sysroot separately to avoid rebuilding it (~10s) on every run. + - name: Sysroot cache + uses: actions/cache@v4 + with: + path: | + src/tests/rust_guests/simpleguest/target/sysroot + src/tests/rust_guests/dummyguest/target/sysroot + src/tests/rust_guests/witguest/target/sysroot + key: sysroot-windows-${{ hashFiles('rust-toolchain.toml') }} + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: "code-checks-windows" + cache-on-failure: "true" + workspaces: | + . -> target + src/tests/rust_guests/simpleguest -> target + src/tests/rust_guests/dummyguest -> target + src/tests/rust_guests/witguest -> target + + - name: Ensure up-to-date Cargo.lock + run: cargo fetch --locked + + - name: fmt + run: just fmt-check + + - name: clippy (debug) + run: | + just clippy debug + just clippy-guests debug + + - name: clippy (release) + run: | + just clippy release + just clippy-guests release + + - name: Verify MSRV + run: ./dev/verify-msrv.sh hyperlight-host hyperlight-guest hyperlight-guest-bin hyperlight-common + + - name: Check cargo features compile + run: just check + + - name: Check compilation with no default features + run: | + just test-compilation-no-default-features debug + just test-compilation-no-default-features release diff --git a/.github/workflows/dep_fuzzing.yml b/.github/workflows/dep_fuzzing.yml index 5422ada5f..3458c56c5 100644 --- a/.github/workflows/dep_fuzzing.yml +++ b/.github/workflows/dep_fuzzing.yml @@ -18,7 +18,6 @@ on: default: "false" permissions: - id-token: write contents: read jobs: @@ -38,17 +37,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Set up nightly rust - uses: dtolnay/rust-toolchain@nightly - - - name: Build rust binaries - run: | - # use these commands in favor of build-and-move-rust-guests to avoid building debug - just build-rust-guests release - just move-rust-guests release + - name: Download Rust guests + uses: actions/download-artifact@v6 + with: + name: rust-guests-release + path: src/tests/rust_guests/bin/release/ - name: Install cargo-fuzz - run: cargo install cargo-fuzz + run: command -v cargo-fuzz >/dev/null 2>&1 || cargo install cargo-fuzz - name: Run Fuzzing run: just fuzz-timed ${{ matrix.target }} ${{ inputs.max_total_time }} diff --git a/.github/workflows/dep_run_examples.yml b/.github/workflows/dep_run_examples.yml new file mode 100644 index 000000000..33fb75f42 --- /dev/null +++ b/.github/workflows/dep_run_examples.yml @@ -0,0 +1,88 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Run Examples + +on: + workflow_call: + inputs: + docs_only: + description: Skip building if docs only + required: false + type: string + default: "false" + hypervisor: + description: Hypervisor for this run (passed from caller matrix) + required: true + type: string + config: + description: Build configuration for this run (passed from caller matrix) + required: true + type: string + cpu: + description: CPU architecture for the build (passed from caller matrix) + required: true + type: string + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + run-examples: + if: ${{ inputs.docs_only == 'false' }} + timeout-minutes: 15 + runs-on: ${{ fromJson( + format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-{2}"]', + (inputs.hypervisor == 'hyperv' || inputs.hypervisor == 'hyperv-ws2025') && 'Windows' || 'Linux', + inputs.hypervisor == 'hyperv' && 'win2022' || inputs.hypervisor == 'hyperv-ws2025' && 'win2025' || inputs.hypervisor == 'mshv3' && 'azlinux3-mshv' || inputs.hypervisor, + inputs.cpu)) }} + steps: + - uses: actions/checkout@v6 + + - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix cargo home permissions + if: runner.os == 'Linux' + run: | + sudo chown -R $(id -u):$(id -g) /opt/cargo || true + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: "${{ runner.os }}-${{ inputs.config }}" + save-if: "false" + + - name: Download Rust guests + uses: actions/download-artifact@v6 + with: + name: rust-guests-${{ inputs.config }} + path: src/tests/rust_guests/bin/${{ inputs.config }}/ + + - name: Download C guests + uses: actions/download-artifact@v6 + with: + name: c-guests-${{ inputs.config }} + path: src/tests/c_guests/bin/${{ inputs.config }}/ + + - name: Run Rust examples - windows + if: runner.os == 'Windows' + env: + RUST_LOG: debug + run: just run-rust-examples ${{ inputs.config }} + + - name: Run Rust examples - linux + if: runner.os != 'Windows' + env: + RUST_LOG: debug + run: just run-rust-examples-linux ${{ inputs.config }} diff --git a/.github/workflows/dep_rust.yml b/.github/workflows/dep_rust.yml deleted file mode 100644 index 55a3bbc86..000000000 --- a/.github/workflows/dep_rust.yml +++ /dev/null @@ -1,221 +0,0 @@ -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json - -name: Rust Tests and Lints - -# See README.md in this directory for more information about workflow_call -on: - workflow_call: - inputs: - docs_only: - description: Skip building if docs only - required: false - type: string - default: "false" - target_triple: - description: Target triple for cross-compilation - required: false - type: string - default: "" - hypervisor: - description: Hypervisor for this run (passed from caller matrix) - required: false - type: string - default: "kvm" - config: - description: Build configuration for this run (passed from caller matrix) - required: false - type: string - default: "debug" - cpu: - description: CPU architecture for the build (passed from caller matrix) - required: false - type: string - default: "amd" - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - -permissions: - id-token: write - contents: read - -# The reason for default shell bash is because on our self-hosted windows runners, -# the default shell is powershell, which doesn't work correctly together with `just` commands. -# Even if a command inside a just-recipe fails, github reports the step as successful. -# The problem may or may not be related to our custom windows runner not applying the -# powershell steps outlined here -# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference -defaults: - run: - shell: bash - -jobs: - code-checks: - if: ${{ inputs.docs_only == 'false' && (inputs.hypervisor == 'hyperv-ws2025' || inputs.hypervisor == 'kvm') }} - timeout-minutes: 60 - runs-on: ${{ fromJson( - format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-amd"]', - (inputs.hypervisor == 'hyperv-ws2025') && 'Windows' || 'Linux', - inputs.hypervisor == 'hyperv-ws2025' && 'win2025' || 'kvm')) }} - steps: - - uses: actions/checkout@v6 - - - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 - with: - rust-toolchain: "1.89" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Does not check for updated Cargo.lock files for test rust guests as this causes an issue with this checkwhen deoendabot updates dependencies in common crates - - name: Ensure up-to-date Cargo.lock - run: | - cargo fetch --locked - - - name: fmt - run: just fmt-check - - - name: clippy - if: ${{ (runner.os == 'Windows' )}} - run: | - just clippy ${{ inputs.config }} - just clippy-guests ${{ inputs.config }} - env: - TARGET_TRIPLE: ${{ inputs.target_triple }} - - - name: clippy exhaustive check - if: ${{ (runner.os == 'Linux' )}} - run: | - just clippy-exhaustive ${{ inputs.config }} - env: - TARGET_TRIPLE: ${{ inputs.target_triple }} - - - name: Verify MSRV - run: ./dev/verify-msrv.sh hyperlight-host hyperlight-guest hyperlight-guest-bin hyperlight-common - - - name: Check hyperlight-guest builds for 32-bit (Nanvix compatibility) - if: ${{ runner.os == 'Linux' }} - run: | - rustup target add i686-unknown-linux-gnu - just check-guest-i686 ${{ inputs.config }} - - build: - if: ${{ inputs.docs_only == 'false' }} - timeout-minutes: 60 - runs-on: ${{ fromJson( - format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-{2}"]', - (inputs.hypervisor == 'hyperv' || inputs.hypervisor == 'hyperv-ws2025') && 'Windows' || 'Linux', - inputs.hypervisor == 'hyperv' && 'win2022' || inputs.hypervisor == 'hyperv-ws2025' && 'win2025' || inputs.hypervisor == 'mshv3' && 'azlinux3-mshv' || inputs.hypervisor, - inputs.cpu)) }} - steps: - - uses: actions/checkout@v6 - - # For rust-fmt - - name: Set up nightly rust - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt - - - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 - with: - rust-toolchain: "1.89" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get gh action service name - if: ${{ (runner.os == 'Windows' )}} - run: (Get-Service actions.runner.*) | Foreach { $_.Name, $_.UserName, $_.ServiceType } - shell: pwsh - - - name: Build and move Rust guests - run: | - # use these commands in favor of build-and-move-rust-guests to avoid building both configs - just build-rust-guests ${{ inputs.config }} - just move-rust-guests ${{ inputs.config }} - - - name: Build c guests - run: | - # use these commands in favor of build-and-move-c-guests to avoid building both configs - just build-c-guests ${{ inputs.config }} - just move-c-guests ${{ inputs.config }} - - - name: Build - run: just build ${{ inputs.config }} - env: - TARGET_TRIPLE: ${{ inputs.target_triple }} - - - name: Run Miri tests - if: ${{ (runner.os == 'Linux' )}} - env: - CARGO_TERM_COLOR: always - TARGET_TRIPLE: ${{ inputs.target_triple }} - run: just miri-tests - - - name: Run Rust tests - env: - CARGO_TERM_COLOR: always - TARGET_TRIPLE: ${{ inputs.target_triple }} - run: | - # with default features - just test ${{ inputs.config }} - - # with only one driver enabled (driver mshv/kvm feature is ignored on windows) - just test ${{ inputs.config }} ${{ inputs.hypervisor == 'mshv3' && 'mshv3' || 'kvm' }} - - # make sure certain cargo features compile - just check - - # without any features - just test-compilation-no-default-features ${{ inputs.config }} - - # One of the examples is flaky on Windows GH runners, so this allows us to disable it for now - - name: Run Rust examples - windows - if: ${{ (runner.os == 'Windows') }} - env: - CARGO_TERM_COLOR: always - RUST_LOG: debug - TARGET_TRIPLE: ${{ inputs.target_triple }} - run: just run-rust-examples ${{ inputs.config }} - - - - name: Run Rust examples - linux - if: ${{ (runner.os != 'Windows') }} - env: - CARGO_TERM_COLOR: always - RUST_LOG: debug - TARGET_TRIPLE: ${{ inputs.target_triple }} - run: just run-rust-examples-linux ${{ inputs.config }} - - - name: Run Rust Gdb tests - env: - CARGO_TERM_COLOR: always - RUST_LOG: debug - TARGET_TRIPLE: ${{ inputs.target_triple }} - run: just test-rust-gdb-debugging ${{ inputs.config }} - - - name: Run Rust Crashdump tests - env: - CARGO_TERM_COLOR: always - RUST_LOG: debug - TARGET_TRIPLE: ${{ inputs.target_triple }} - run: just test-rust-crashdump ${{ inputs.config }} - - - name: Run Rust Tracing tests - linux - if: runner.os == 'Linux' - env: - CARGO_TERM_COLOR: always - RUST_LOG: debug - TARGET_TRIPLE: ${{ inputs.target_triple }} - run: just test-rust-tracing ${{ inputs.config }} - - - name: Download benchmarks from "latest" - run: just bench-download ${{ runner.os }} ${{ inputs.hypervisor }} ${{ inputs.cpu}} dev-latest # compare to prerelease - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - continue-on-error: true - if: ${{ inputs.config == 'release' && inputs.target_triple == '' }} - - - name: Run benchmarks - run: | - just bench-ci main - if: ${{ inputs.config == 'release' && inputs.target_triple == '' }} diff --git a/Justfile b/Justfile index 4a1d206d5..1b920d9cf 100644 --- a/Justfile +++ b/Justfile @@ -43,12 +43,12 @@ build target=default-target: guests: build-and-move-rust-guests build-and-move-c-guests witguest-wit: - cargo install --locked wasm-tools + command -v wasm-tools >/dev/null 2>&1 || cargo install --locked wasm-tools cd src/tests/rust_guests/witguest && wasm-tools component wit guest.wit -w -o interface.wasm cd src/tests/rust_guests/witguest && wasm-tools component wit two_worlds.wit -w -o twoworlds.wasm build-rust-guests target=default-target features="": (witguest-wit) - cargo install --locked cargo-hyperlight + command -v cargo-hyperlight >/dev/null 2>&1 || cargo install --locked cargo-hyperlight cd src/tests/rust_guests/simpleguest && cargo hyperlight build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} cd src/tests/rust_guests/dummyguest && cargo hyperlight build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} cd src/tests/rust_guests/witguest && cargo hyperlight build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} @@ -209,16 +209,16 @@ check-guest-i686 target=default-target: test-doc target=default-target features="": {{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} {{ if features =="" {''} else { "--features " + features } }} --doc -################ -### LINTING #### -################ - miri-tests: - rustup component add miri --toolchain nightly + rustup +nightly component list | grep -q "miri.*installed" || rustup component add miri --toolchain nightly # For now only run miri tests on hyperlight-common with trace_guest feature # We can add more as needed cargo +nightly miri test -p hyperlight-common -F trace_guest +################ +### LINTING #### +################ + check: {{ cargo-cmd }} check {{ target-triple-flag }} {{ cargo-cmd }} check -p hyperlight-host --features crashdump {{ target-triple-flag }} @@ -227,6 +227,7 @@ check: {{ cargo-cmd }} check -p hyperlight-host --features trace_guest,mem_profile {{ target-triple-flag }} fmt-check: + rustup +nightly component list | grep -q "rustfmt.*installed" || rustup component add rustfmt --toolchain nightly cargo +nightly fmt --all -- --check cargo +nightly fmt --manifest-path src/tests/rust_guests/simpleguest/Cargo.toml -- --check cargo +nightly fmt --manifest-path src/tests/rust_guests/dummyguest/Cargo.toml -- --check @@ -237,6 +238,7 @@ check-license-headers: ./dev/check-license-headers.sh fmt-apply: + rustup +nightly component list | grep -q "rustfmt.*installed" || rustup component add rustfmt --toolchain nightly cargo +nightly fmt --all cargo +nightly fmt --manifest-path src/tests/rust_guests/simpleguest/Cargo.toml cargo +nightly fmt --manifest-path src/tests/rust_guests/dummyguest/Cargo.toml @@ -252,7 +254,7 @@ clippyw target=default-target: (witguest-wit) {{ cargo-cmd }} clippy --all-features --target x86_64-pc-windows-gnu --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings clippy-guests target=default-target: (witguest-wit) - cargo install --locked cargo-hyperlight + command -v cargo-hyperlight >/dev/null 2>&1 || cargo install --locked cargo-hyperlight cd src/tests/rust_guests/simpleguest && cargo hyperlight clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings cd src/tests/rust_guests/witguest && cargo hyperlight clippy --profile={{ if target == "debug" { "dev" } else { target } }} -- -D warnings @@ -324,7 +326,7 @@ tar-static-lib: (build-rust-capi "release") (build-rust-capi "debug") # If tag is not given, defaults to latest release # Options for os: "Windows", or "Linux" # Options for Linux hypervisor: "kvm", "mshv3" -# Options for Windows hypervisor: "hyperv" +# Options for Windows hypervisor: "hyperv", "hyperv-ws2025" # Options for cpu: "amd", "intel" bench-download os hypervisor cpu tag="": gh release download {{ tag }} -D ./target/ -p benchmarks_{{ os }}_{{ hypervisor }}_{{ cpu }}.tar.gz diff --git a/src/hyperlight_host/src/mem/shared_mem.rs b/src/hyperlight_host/src/mem/shared_mem.rs index 526e9fea2..824cfde04 100644 --- a/src/hyperlight_host/src/mem/shared_mem.rs +++ b/src/hyperlight_host/src/mem/shared_mem.rs @@ -1247,21 +1247,26 @@ mod tests { } else { vec![] }; - let status = std::process::Command::new("cargo") - .args(["test", "-p", "hyperlight-host"]) + let output = std::process::Command::new("cargo") + .args(["test", "-p", "hyperlight-host", "--lib"]) .args(target_args) .args(["--", "--ignored", test]) .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status() + .output() .expect("Unable to launch tests"); - assert_eq!( - status.code(), - Some(TEST_EXIT_CODE.into()), - "Guard Page test failed: {}", - test - ); + let exit_code = output.status.code(); + if exit_code != Some(TEST_EXIT_CODE.into()) { + eprintln!("=== Guard Page test '{}' failed ===", test); + eprintln!("Exit code: {:?} (expected {})", exit_code, TEST_EXIT_CODE); + eprintln!("=== STDOUT ==="); + eprintln!("{}", String::from_utf8_lossy(&output.stdout)); + eprintln!("=== STDERR ==="); + eprintln!("{}", String::from_utf8_lossy(&output.stderr)); + panic!( + "Guard Page test failed: {} (exit code {:?}, expected {})", + test, exit_code, TEST_EXIT_CODE + ); + } } } }