🚀 在 VS Code 中

改善 CI 建置時間

2020 年 2 月 18 日,作者:Ethan Dennis、@erdennis13 和 João Moreno、@joaomoreno

Visual Studio Code 是一個大型專案,包含許多活動部件和活躍的參與者名單。我們已展示我們如何積極使用 Azure Pipelines 來維持良好的工程實務,方法是維護我們的建置和持續整合基礎架構。在這篇部落格文章中,我們將討論如何使用 Azure Pipelines Artifact Caching Tasks 來大幅縮短我們的 CI 建置時間。

我們在先前的部落格文章中描述了我們如何將 CI 建置時間縮短了 33%。這是透過使用自訂建置工作來完成的,這些工作會快取 VS Code 使用的節點模組,而不是在建置時解析套件。雖然我們對效能提升感到滿意,但我們想看看可以將我們建置的快取工作推進多遠。

當我們上次談論我們的 CI 工程時,我們的目標平台涵蓋 Windows、macOS 和 Linux。截至今日,VS Code 的目標平台更加多樣化,例如適用於其遠端伺服器元件的 Arm64 和 Alpine Linux。總共有八個不同的目標,它們都共用常見的建置步驟。這篇文章概述了我們如何利用快取工作來減少 CI 重複並進一步改善我們的建置時間。

仍有改進空間

那麼,所有建置作業之間的常見步驟究竟是什麼?每個建置目標都有一個作業,它遵循一組類似的步驟。從非常高的層次來看,每個作業都必須

  1. 還原相依性
  2. Lint TypeScript 和 JavaScript
  3. 將 TypeScript 編譯為 JavaScript
  4. 執行單元測試套件
  5. 執行整合測試套件
  6. 封裝 VS Code

我們的快取工作是加速還原相依性步驟的明顯選擇。例如,當 package-lock.json 檔案很少變更時,為什麼要執行昂貴的 npm install 步驟,而不是快取先前執行的結果?由於我們先前已討論過快取套件,因此這篇文章有趣之處在於我們如何將快取應用於其他步驟。

由於 lint 和編譯與平台無關,因此這些步驟可以輕鬆地由單一建置代理程式執行,該代理程式會與其他平台相關的代理程式共用其結果,而不是讓所有代理程式重複執行此工作。我們建立了一個 Linux 建置代理程式,其唯一職責正是如此:還原套件、lint 和編譯原始碼。我們所要做的就是與其他代理程式共用結果。

快取所有內容

為了跨建置代理程式共用快取結果,我們需要與平台無關的快取,這最初不受快取工作支援。因此,在 Azure Pipelines Artifact Caching Tasks 中新增了一個可選的 platformIndependent 參數。

以下是 VS Code 如何使用 platformIndependent 參數

- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
  inputs:
    keyfile: keyfile
    targetfolder: target
    vstsFeed: $(ArtifactFeed)
    platformIndependent: true

在快取節點模組時,使用 package-lock.json 檔案作為快取金鑰是合乎邏輯的。當此檔案變更時,我們必須使快取失效。在快取編譯輸出時,整個程式碼庫必須充當快取金鑰。為了簡化操作,我們決定使用 HEAD commit 作為快取金鑰,因為新的 commit 不可避免地會建立新的快取項目。這對我們的目的來說運作良好,因為單一建置(儘管跨建置代理程式執行)始終在單一 commit 上執行。

另一個缺少的功能是每個建置作業建立多個快取的能力。我們現在發現自己要處理兩個快取(節點模組、編譯),但無法個別處理每個快取。快取工作會輸出一個名為 CacheRestored 的環境變數,可用於樂觀地跳過建置工作。此環境變數在與單一快取互動的建置中運作良好,但在多個快取中不太好用,讓我們想知道 CacheRestored 指的是哪個快取。再次地,在 Azure Pipelines Artifact Caching Tasks 中新增了另一個可選的 alias 參數。

以下是我們如何使用 alias 參數

- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
  inputs:
    keyfile: "yarn.lock"
    targetfolder: "node_modules"
    vstsFeed: "$(ArtifactFeed)"
    alias: "Packages"

- script: |
    yarn install
  displayName: Install Dependencies
  condition: ne(variables['CacheRestored-Packages'], 'true')

在這裡,別名 Packages 會附加到環境變數輸出,讓我們可以在單一建置作業中快取 NPM 套件和編譯輸出。我們終於消除了許多 CI 工作的重複,這些工作現在可以只執行一次並在跨平台特定的代理程式之間共用。

在給定的特定使用案例中,仍然有一個最終最佳化的空間:建置重新提交。有時我們必須在先前建置的 commit 上重新觸發 VS Code 建置,因為測試可能不穩定或某些代理程式可能會隨機失敗。理想情況下,共用代理程式不會還原或重新編譯通用程式碼,而是會將控制權移交給平台相關的代理程式來執行其工作。我們注意到的問題是,編譯快取套件非常龐大,還原它們需要大約 8 分鐘,但這是徒勞的,因為如果快取存在,共用代理程式只會讓出控制權。因此,再次在 Azure Pipelines Artifact Caching Tasks 中新增了一個新的可選 dryRun 參數,這讓我們可以檢查快取套件是否存在,而無需還原它,有效地將我們的建置重新提交時間縮短了 8 分鐘。

在我們的建置中使用 dryRun 參數看起來像這樣

- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
  inputs:
    keyfile: commit
    targetfolder: output
    vstsFeed: "$(ArtifactFeed)"
    dryRun: true

- script: |
    npm run compile install
  displayName: Install Dependencies
  condition: ne(variables['CacheExists'], 'true')

請注意,這也引入了一個新的 CacheExists 變數,它與 dryRun 參數一起運作。

結果

一旦實作這些變更,我們看到總建置時間大幅縮短。下表顯示了 VS Code 目標的每個平台的總建置時間變化

平台 之前 之後 節省時間
Windows 58 分鐘 44 分鐘 24%
Windows 32 59 分鐘 46 分鐘 22%
Linux 38 分鐘 23 分鐘 39%
macOS 68 分鐘 42 分鐘 38%
Linux Arm 22 分鐘 21 分鐘 5%
Linux Alpine 23 分鐘 26 分鐘 -13%

VS Code before and after build times

Linux Arm 和 Linux Alpine 目標僅建置 VS Code 遠端伺服器元件,因此它們的原始建置時間已足夠好。但由於它們與標準 VS Code 用戶端平台共用一些常見工作,因此我們決定讓它們依賴於通用建置代理程式。由於在某種情況下增加了額外負荷,因此導致建置時間略有增加。

建置重新提交看到了大幅改善,因為可以完全跳過共用代理程式工作。以下是一些 macOS 的數字範例

平台 之前 之後 節省時間
macOS 68 秒 34 秒 50%

總而言之,我們很高興看到 VS Code 的 CI 建置時間總共縮短了約 50%!最好的消息是,您可以從我們的建置定義中汲取靈感,以實現您自己的建置時間改進。

快取愉快,

Ethan Dennis,開發人員服務資深軟體工程師 @erdennis13

João Moreno,VS Code 資深軟體工程師 @joaomoreno