Windows tests on self-hosted runners (#29764)

Peter Tripp , Max Brunsfeld , and Junkui Zhang created

Windows self-hosted runners

Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Junkui Zhang <364772080@qq.com>

Change summary

.github/actions/run_tests_windows/action.yml |   8 
.github/workflows/ci.yml                     | 108 ++++-----------------
script/clear-target-dir-if-larger-than.ps1   |  22 ++++
script/clippy.ps1                            |  25 +++-
script/install-rustup.ps1                    |  39 +++++++
5 files changed, 104 insertions(+), 98 deletions(-)

Detailed changes

.github/actions/run_tests_windows/action.yml 🔗

@@ -10,8 +10,8 @@ inputs:
 runs:
   using: "composite"
   steps:
-    - name: Install Rust
-      shell: pwsh
+    - name: Install test runner
+      shell: powershell
       working-directory: ${{ inputs.working-directory }}
       run: cargo install cargo-nextest --locked
 
@@ -21,6 +21,6 @@ runs:
         node-version: "18"
 
     - name: Run tests
-      shell: pwsh
+      shell: powershell
       working-directory: ${{ inputs.working-directory }}
-      run: cargo nextest run --workspace --no-fail-fast --config='profile.dev.debug="limited"'
+      run: cargo nextest run --workspace --no-fail-fast

.github/workflows/ci.yml 🔗

@@ -373,116 +373,52 @@ jobs:
         if: always()
         run: rm -rf ./../.cargo
 
-  windows_clippy:
+  windows_tests:
     timeout-minutes: 60
-    name: (Windows) Run Clippy
+    name: (Windows) Run Tests
     needs: [job_spec]
     if: |
       github.repository_owner == 'zed-industries' &&
       needs.job_spec.outputs.run_tests == 'true'
-    runs-on: windows-2025-16
+    runs-on: [self-hosted, Windows, X64]
     steps:
-      # more info here:- https://github.com/rust-lang/cargo/issues/13020
-      - name: Enable longer pathnames for git
-        run: git config --system core.longpaths true
+      - name: Environment Setup
+        run: |
+          $RunnerDir = Split-Path -Parent $env:RUNNER_WORKSPACE
+          Write-Output `
+            "RUSTUP_HOME=$RunnerDir\.rustup" `
+            "CARGO_HOME=$RunnerDir\.cargo" `
+            "PATH=$RunnerDir\.cargo\bin;$env:PATH" `
+          >> $env:GITHUB_ENV
 
       - name: Checkout repo
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
         with:
           clean: false
 
-      - name: Create Dev Drive using ReFS
-        run: ./script/setup-dev-driver.ps1
-
-      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
-      - name: Copy Git Repo to Dev Drive
-        run: |
-          Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
-
-      - name: Cache dependencies
-        uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
-        with:
-          save-if: ${{ github.ref == 'refs/heads/main' }}
-          workspaces: ${{ env.ZED_WORKSPACE }}
-          cache-provider: "github"
-
-      - name: Configure CI
+      - name: Setup Cargo and Rustup
         run: |
           mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
           cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
+          .\script\install-rustup.ps1
 
       - name: cargo clippy
-        working-directory: ${{ env.ZED_WORKSPACE }}
-        run: ./script/clippy.ps1
-
-      - name: Check dev drive space
-        working-directory: ${{ env.ZED_WORKSPACE }}
-        # `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
-        run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
-
-      # Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
-      - name: Clean CI config file
-        if: always()
-        run: |
-          if (Test-Path "${{ env.CARGO_HOME }}/config.toml") {
-            Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml"  -Force
-          }
-
-  # Windows CI takes twice as long as our other platforms and fast github hosted runners are expensive.
-  # But we still want to do CI, so let's only run tests on main and come back to this when we're
-  # ready to self host our Windows CI (e.g. during the push for full Windows support)
-  windows_tests:
-    timeout-minutes: 60
-    name: (Windows) Run Tests
-    needs: [job_spec]
-    if: |
-      github.repository_owner == 'zed-industries' &&
-      needs.job_spec.outputs.run_tests == 'true'
-    # Use bigger runners for PRs (speed); smaller for async (cost)
-    runs-on: ${{ github.event_name == 'pull_request' && 'windows-2025-32' || 'windows-2025-16' }}
-    steps:
-      # more info here:- https://github.com/rust-lang/cargo/issues/13020
-      - name: Enable longer pathnames for git
-        run: git config --system core.longpaths true
-
-      - name: Checkout repo
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
-        with:
-          clean: false
-
-      - name: Create Dev Drive using ReFS
-        run: ./script/setup-dev-driver.ps1
-
-      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
-      - name: Copy Git Repo to Dev Drive
         run: |
-          Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
-
-      - name: Cache dependencies
-        uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
-        with:
-          save-if: ${{ github.ref == 'refs/heads/main' }}
-          workspaces: ${{ env.ZED_WORKSPACE }}
-          cache-provider: "github"
-
-      - name: Configure CI
-        run: |
-          mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
-          cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
+          .\script\clippy.ps1
 
       - name: Run tests
         uses: ./.github/actions/run_tests_windows
-        with:
-          working-directory: ${{ env.ZED_WORKSPACE }}
 
       - name: Build Zed
-        working-directory: ${{ env.ZED_WORKSPACE }}
         run: cargo build
 
-      - name: Check dev drive space
-        working-directory: ${{ env.ZED_WORKSPACE }}
-        # `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
-        run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
+      - name: Limit target directory size
+        run: ./script/clear-target-dir-if-larger-than.ps1 250
+
+      # - name: Check dev drive space
+      #   working-directory: ${{ env.ZED_WORKSPACE }}
+      #   # `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
+      #   run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
 
       # Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
       - name: Clean CI config file
@@ -505,7 +441,6 @@ jobs:
       - linux_tests
       - build_remote_server
       - macos_tests
-      - windows_clippy
       - windows_tests
     if: |
       github.repository_owner == 'zed-industries' &&
@@ -525,7 +460,6 @@ jobs:
             [[ "${{ needs.macos_tests.result }}"          != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
             [[ "${{ needs.linux_tests.result }}"          != 'success' ]] && { RET_CODE=1; echo "Linux tests failed"; }
             [[ "${{ needs.windows_tests.result }}"        != 'success' ]] && { RET_CODE=1; echo "Windows tests failed"; }
-            [[ "${{ needs.windows_clippy.result }}"       != 'success' ]] && { RET_CODE=1; echo "Windows clippy failed"; }
             [[ "${{ needs.build_remote_server.result }}"  != 'success' ]] && { RET_CODE=1; echo "Remote server build failed"; }
             # This check is intentionally disabled. See: https://github.com/zed-industries/zed/pull/28431
             # [[ "${{ needs.migration_checks.result }}"     != 'success' ]] && { RET_CODE=1; echo "Migration Checks failed"; }

script/clear-target-dir-if-larger-than.ps1 🔗

@@ -0,0 +1,22 @@
+param (
+    [Parameter(Mandatory = $true)]
+    [int]$MAX_SIZE_IN_GB
+)
+
+$ErrorActionPreference = "Stop"
+$PSNativeCommandUseErrorActionPreference = $true
+$ProgressPreference = "SilentlyContinue"
+
+if (-Not (Test-Path -Path "target")) {
+    Write-Host "target directory does not exist yet"
+    exit 0
+}
+
+$current_size_gb = (Get-ChildItem -Recurse -Force -File -Path "target" | Measure-Object -Property Length -Sum).Sum / 1GB
+
+Write-Host "target directory size: ${current_size_gb}GB. max size: ${MAX_SIZE_IN_GB}GB"
+
+if ($current_size_gb -gt $MAX_SIZE_IN_GB) {
+    Write-Host "clearing target directory"
+    Remove-Item -Recurse -Force -Path "target\*"
+}

script/clippy.ps1 🔗

@@ -1,21 +1,32 @@
 $ErrorActionPreference = "Stop"
 
+Write-Host "Your PATH entries:"
+$env:Path -split ";" | ForEach-Object { Write-Host "  $_" }
+
 $needAddWorkspace = $false
-if ($args -notcontains "-p" -and $args -notcontains "--package") {
+if ($args -notcontains "-p" -and $args -notcontains "--package")
+{
     $needAddWorkspace = $true
 }
 
 # https://stackoverflow.com/questions/41324882/how-to-run-a-powershell-script-with-verbose-output/70020655#70020655
-Set-PSDebug -Trace 2
+# Set-PSDebug -Trace 2
 
-$Cargo = $env:CARGO
-if (-not $Cargo) {
+if ($env:CARGO)
+{
+    $Cargo = $env:CARGO
+} elseif (Get-Command "cargo" -ErrorAction SilentlyContinue)
+{
     $Cargo = "cargo"
+} else
+{
+    Write-Error "Could not find cargo in path." -ErrorAction Stop
 }
 
-if ($needAddWorkspace) {
+if ($needAddWorkspace)
+{
     & $Cargo clippy @args --workspace --release --all-targets --all-features -- --deny warnings
-}
-else {
+} else
+{
     & $Cargo clippy @args --release --all-targets --all-features -- --deny warnings
 }

script/install-rustup.ps1 🔗

@@ -0,0 +1,39 @@
+# Checks if cargo is in the user's path or in default install path
+# If not, download with rustup-installer (which respects CARGO_HOME / RUSTUP_HOME)
+
+# Like 'set -e' in bash
+$ErrorActionPreference = "Stop"
+
+$cargoHome = if ($env:CARGO_HOME) { $env:CARGO_HOME } else { "$env:USERPROFILE\.cargo" }
+$rustupPath = "$cargoHome\bin\rustup.exe"
+$cargoPath = "$cargoHome\bin\cargo.exe"
+
+# Check if cargo is already available in path
+if (Get-Command cargo -ErrorAction SilentlyContinue)
+{
+    cargo --version
+    exit
+}
+# Check if rustup and cargo are available in CARGO_HOME
+elseif (-not ((Test-Path $rustupPath) -and (Test-Path $cargoPath))) {
+    Write-Output "Rustup or Cargo not found in $cargoHome, installing..."
+
+    $tempDir = [System.IO.Path]::GetTempPath()
+
+    # Download and install rustup
+    $RustupInitPath = "$tempDir\rustup-init.exe"
+    Write-Output "Downloading rustup installer..."
+    Invoke-WebRequest `
+        -OutFile $RustupInitPath `
+        -Uri https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe
+
+    Write-Output "Installing rustup..."
+    & $RustupInitPath -y --default-toolchain none
+    Remove-Item -Force $RustupInitPath
+
+    Write-Output "Rust installation complete."
+    # This is necessary
+}
+
+& $rustupPath --version
+& $cargoPath --version