diff --git a/.github/actions/setup-electron/action.yml b/.github/actions/setup-electron/action.yml new file mode 100644 index 000000000000..2aaef4c1b082 --- /dev/null +++ b/.github/actions/setup-electron/action.yml @@ -0,0 +1,98 @@ +name: Setup Electron +description: Restores cached Electron archives, validates and prepares Electron, and optionally saves the cache. + +inputs: + arch: + description: Target Electron architecture. + required: true + lookup-only: + description: When 'true', only checks whether the cache entry exists without restoring archives or preparing Electron. + default: 'false' + save-cache: + description: When 'true', saves the archives after successful setup on a cache miss. Ignored when lookup-only is 'true'. + default: 'false' + +outputs: + cache-hit: + description: "'true' when an exact Electron cache key match was found." + value: ${{ steps.restore.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Prepare Electron cache path + if: runner.os != 'Windows' + shell: bash + run: | + if [ "$RUNNER_OS" = "macOS" ]; then + echo "ELECTRON_CACHE_PATH=$HOME/Library/Caches/electron" >> "$GITHUB_ENV" + else + echo "ELECTRON_CACHE_PATH=${XDG_CACHE_HOME:-$HOME/.cache}/electron" >> "$GITHUB_ENV" + fi + + - name: Prepare Electron cache path + if: runner.os == 'Windows' + shell: pwsh + run: | + $cacheRoot = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } else { Join-Path $HOME 'AppData/Local' } + Add-Content -Path $env:GITHUB_ENV -Value "ELECTRON_CACHE_PATH=$(Join-Path $cacheRoot 'electron/Cache')" + + - name: Restore Electron cache + id: restore + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ env.ELECTRON_CACHE_PATH }} + key: "electron-download-v1-${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('.npmrc', 'build/checksums/electron.txt') }}" + lookup-only: ${{ inputs.lookup-only }} + + - name: Download Electron + if: inputs.lookup-only != 'true' && runner.os != 'Windows' + shell: bash + run: | + for i in {1..3}; do + if npm run electron -- "$VSCODE_ARCH"; then + echo "Download successful on attempt $i" + break + fi + + if [ $i -eq 3 ]; then + echo "Download failed after 3 attempts" >&2 + exit 1 + fi + + echo "Download failed on attempt $i, retrying..." + sleep 5 + done + env: + VSCODE_ARCH: ${{ inputs.arch }} + + - name: Download Electron + if: inputs.lookup-only != 'true' && runner.os == 'Windows' + shell: pwsh + run: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + + for ($i = 1; $i -le 3; $i++) { + try { + exec { npm run electron -- $env:VSCODE_ARCH } + break + } + catch { + if ($i -eq 3) { + Write-Error "Download failed after 3 attempts" + throw + } + Write-Host "Download failed attempt $i, retrying..." + Start-Sleep -Seconds 2 + } + } + env: + VSCODE_ARCH: ${{ inputs.arch }} + + - name: Save Electron cache + if: inputs.lookup-only != 'true' && inputs.save-cache == 'true' && steps.restore.outputs.cache-hit != 'true' + uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + with: + path: ${{ env.ELECTRON_CACHE_PATH }} + key: ${{ steps.restore.outputs.cache-primary-key }} diff --git a/.github/workflows/node_modules_cache/README.md b/.github/workflows/node_modules_cache/README.md index 00ea96811192..5ebb82c00afc 100644 --- a/.github/workflows/node_modules_cache/README.md +++ b/.github/workflows/node_modules_cache/README.md @@ -33,3 +33,39 @@ Example usage from a workflow step: If you change the archive format or flags, update both the `archive` and `extract` branch of the script, and bump `build/.cachesalt` to invalidate existing caches. + +## Electron download cache + +The platform jobs in [pr-node-modules.yml](../pr-node-modules.yml) also warm a +separate Electron download cache on `main`. The +[setup-electron](../../actions/setup-electron/action.yml) action shares cache +handling and download retries with the Linux, macOS, and Windows PR test +workflows: + +- By default, restore cached archives and validate/prepare Electron, downloading + with up to three attempts as needed. PR jobs use this mode without saving. +- `lookup-only: 'true'` only checks for a cache entry, without restoring archives, + preparing Electron, or saving. This mode does not require npm dependencies. +- `save-cache: 'true'` also saves after successful setup on a cache miss. + +The cache stores `@electron/get`'s original download archives, not the prepared +`.build/electron` application. Its key includes the runner OS, target +architecture, and a hash of `.npmrc` and `build/checksums/electron.txt`. Ordinary +`package-lock.json` changes do not invalidate it. If a downloader update changes +the cache format, bump the `electron-download-v1` prefix in `setup-electron`. + +The paths match `@electron/get`'s platform defaults, including `XDG_CACHE_HOME` +on Linux and `LOCALAPPDATA` on Windows. Cache paths and keys are handled within +the action. + +Even on a cache hit, PR jobs run `npm run electron` to validate and prepare the +application from the cached archives using the current checkout. Checksum +validation remains enabled: the current downloader still fetches +`SHASUMS256.txt` from GitHub, so this cache avoids the large ZIP downloads but +does not make Electron setup entirely network-independent. + +On `main`, an Electron cache hit uses lookup-only checks and skips downloading +Electron. On a miss, the platform job restores `node_modules` (or installs them +if that cache also misses), then invokes `setup-electron` with `save-cache: 'true'`. +This checks the cache again before preparing and saving Electron, while keeping +already-warm jobs from extracting dependency archives unnecessarily. diff --git a/.github/workflows/pr-darwin-test.yml b/.github/workflows/pr-darwin-test.yml index 3fc4fbea2e97..88aa192d0352 100644 --- a/.github/workflows/pr-darwin-test.yml +++ b/.github/workflows/pr-darwin-test.yml @@ -108,25 +108,11 @@ jobs: - name: Transpile client and extensions run: npm run gulp transpile-client-esbuild transpile-extensions - - name: Download Electron + - name: Setup Electron if: ${{ inputs.electron_tests || inputs.remote_tests }} - run: | - set -e - - for i in {1..3}; do # try 3 times (matching retryCountOnTaskFailure: 3) - if npm run electron -- ${{ env.VSCODE_ARCH }}; then - echo "Download successful on attempt $i" - break - fi - - if [ $i -eq 3 ]; then - echo "Download failed after 3 attempts" >&2 - exit 1 - fi - - echo "Download failed on attempt $i, retrying..." - sleep 5 # optional: add a small delay between retries - done + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-linux-test.yml b/.github/workflows/pr-linux-test.yml index 057fb511a3f7..fd2112ff320f 100644 --- a/.github/workflows/pr-linux-test.yml +++ b/.github/workflows/pr-linux-test.yml @@ -308,25 +308,11 @@ jobs: - name: Transpile client and extensions run: npm run gulp transpile-client-esbuild transpile-extensions - - name: Download Electron + - name: Setup Electron if: ${{ inputs.electron_tests || inputs.remote_tests }} - run: | - set -e - - for i in {1..3}; do # try 3 times (matching retryCountOnTaskFailure: 3) - if npm run electron -- ${{ env.VSCODE_ARCH }}; then - echo "Download successful on attempt $i" - break - fi - - if [ $i -eq 3 ]; then - echo "Download failed after 3 attempts" >&2 - exit 1 - fi - - echo "Download failed on attempt $i, retrying..." - sleep 5 # optional: add a small delay between retries - done + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-node-modules.yml b/.github/workflows/pr-node-modules.yml index 8b83a03492fe..89f1b2377c73 100644 --- a/.github/workflows/pr-node-modules.yml +++ b/.github/workflows/pr-node-modules.yml @@ -88,13 +88,20 @@ jobs: with: node-version-file: .nvmrc + - name: Check Electron cache + id: cache-electron + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} + lookup-only: 'true' + - name: Restore node_modules cache id: cache-node-modules uses: ./.github/actions/restore-node-modules with: key-prefix: node_modules-linux key-args: "linux ${{ env.VSCODE_ARCH }} $(node -p process.arch)" - lookup-only: 'true' + lookup-only: ${{ steps.cache-electron.outputs.cache-hit == 'true' }} - name: Install build dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' @@ -143,6 +150,15 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' uses: ./.github/actions/save-node-modules + - name: Setup Electron + if: steps.cache-electron.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} + save-cache: 'true' + env: + GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }} + macOS: name: macOS runs-on: macos-26-xlarge @@ -158,13 +174,20 @@ jobs: with: node-version-file: .nvmrc + - name: Check Electron cache + id: cache-electron + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} + lookup-only: 'true' + - name: Restore node_modules cache id: cache-node-modules uses: ./.github/actions/restore-node-modules with: key-prefix: node_modules-macos key-args: "darwin ${{ env.VSCODE_ARCH }} $(node -p process.arch)" - lookup-only: 'true' + lookup-only: ${{ steps.cache-electron.outputs.cache-hit == 'true' }} - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' @@ -202,6 +225,15 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' uses: ./.github/actions/save-node-modules + - name: Setup Electron + if: steps.cache-electron.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} + save-cache: 'true' + env: + GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }} + windows: name: Windows runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-windows-2022-x64, "JobId=windows-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" ] @@ -217,13 +249,20 @@ jobs: with: node-version-file: .nvmrc + - name: Check Electron cache + id: cache-electron + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} + lookup-only: 'true' + - name: Restore node_modules cache id: node-modules-cache uses: ./.github/actions/restore-node-modules with: key-prefix: node_modules-windows key-args: "win32 ${{ env.VSCODE_ARCH }} $(node -p process.arch)" - lookup-only: 'true' + lookup-only: ${{ steps.cache-electron.outputs.cache-hit == 'true' }} - name: Install dependencies if: steps.node-modules-cache.outputs.cache-hit != 'true' @@ -261,6 +300,15 @@ jobs: if: steps.node-modules-cache.outputs.cache-hit != 'true' uses: ./.github/actions/save-node-modules + - name: Setup Electron + if: steps.cache-electron.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} + save-cache: 'true' + env: + GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }} + copilot-linux: name: Copilot (Linux) runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-ubuntu-22.04-x64, "JobId=copilot-linux-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" ] diff --git a/.github/workflows/pr-win32-test.yml b/.github/workflows/pr-win32-test.yml index 8680fae6cf28..576575957ffe 100644 --- a/.github/workflows/pr-win32-test.yml +++ b/.github/workflows/pr-win32-test.yml @@ -116,27 +116,11 @@ jobs: shell: pwsh run: npm run gulp "transpile-client-esbuild" "transpile-extensions" - - name: Download Electron + - name: Setup Electron if: ${{ inputs.electron_tests || inputs.remote_tests }} - shell: pwsh - run: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - - for ($i = 1; $i -le 3; $i++) { - try { - exec { npm run electron -- ${{ env.VSCODE_ARCH }} } - break - } - catch { - if ($i -eq 3) { - Write-Error "Download failed after 3 attempts" - throw - } - Write-Host "Download failed attempt $i, retrying..." - Start-Sleep -Seconds 2 - } - } + uses: ./.github/actions/setup-electron + with: + arch: ${{ env.VSCODE_ARCH }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}