從 Gradle 5.1 開始,我們建議在建立任務時使用設定避免 API。

writing tasks 4

任務設定避免 API

設定避免 API 會避免設定不會用於建置的任務,這可能會對總設定時間產生重大影響。

例如,在執行 compile 任務(套用 java 外掛)時,其他不相關的任務(例如 cleantestjavadocs)將不會執行。

若要避免建立和設定建置不需要的任務,我們可以改為註冊該任務。

註冊任務時,建置會知道該任務。可以設定任務,並且可以傳遞對任務的參照,但任務物件本身尚未建立,而且其動作尚未執行。已註冊的任務將保持此狀態,直到建置中的某個項目需要已實例化的任務物件為止。如果永遠不需要任務物件,則任務將保持註冊狀態,並且將避免建立和設定任務的成本。

在 Gradle 中,您可以使用 TaskContainer.register(java.lang.String) 註冊任務。register(…​) 方法不會傳回任務實例,而是傳回 TaskProvider,這是任務的參照,可以在許多可能使用一般任務物件的地方使用(例如,在建立任務相依性時)。

指南

延後任務建立

有效避免任務設定需要建立作者將 TaskContainer.create(java.lang.String) 的實例變更為 TaskContainer.register(java.lang.String)

較舊版本的 Gradle 只支援 create(…​) API。create(…​) API 在呼叫時會急切建立並設定任務,應避免使用。

單獨使用 register(…​) 可能不足以完全避免所有任務設定。您可能需要變更其他透過名稱或類型設定任務的程式碼,請參閱下方說明。

延後任務設定

急切 API 例如 DomainObjectCollection.all(org.gradle.api.Action)DomainObjectCollection.withType(java.lang.Class, org.gradle.api.Action) 會立即建立並設定任何已註冊的任務。若要延後任務設定,您必須移轉到等效的避免設定 API。請參閱 下表 以找出最佳替代方案。

參照已註冊的任務

您可以透過 TaskProvider 物件處理已註冊的任務,而非參照任務物件。可以透過多種方式取得 TaskProvider,包括 TaskContainer.register(java.lang.String)TaskCollection.named(java.lang.String) 方法。

呼叫 Provider.get() 或使用 TaskCollection.getByName(java.lang.String) 根據名稱查詢任務,將會建立並設定任務。

Task.dependsOn(java.lang.Object…​)ConfigurableFileCollection.builtBy(java.lang.Object...) 這類方法會以與 Task 相同的方式處理 TaskProvider,因此您不需要取消封裝 Provider,顯式依賴關係才能繼續運作。

您必須使用避免設定的等效方式,才能根據名稱設定任務。請參閱 下表 以找出最佳替代方案。

參照任務實例

如果您需要存取 Task 實例,可以使用 TaskCollection.named(java.lang.String)Provider.get()。這將會建立並設定任務,但所有內容應會像使用急切 API 時一樣運作。

避免設定的任務排序

呼叫排序方法本身不會建立任務。這些方法所做的只是宣告關聯性。

這些關係的存在可能會在建置程序的後續階段間接導致任務建立。

當需要建立任務關係時 (例如,dependsOnfinalizedBymustRunAftershouldRunAfter),可以區分軟關係和強關係。它們對組態階段中任務建立的影響不同

遷移指南

以下各節將說明在遷移建置邏輯時應遵守的一些 一般準則。我們也提供了一些建議的 步驟 來遵循,以及 疑難排解常見陷阱

遷移準則

  1. 在遷移期間使用 help 任務作為基準。
    help 任務是評量遷移程序的完美候選者。在僅使用組態避免 API 的建置中,建置掃描 顯示組態期間未建立任何任務,且僅建立已執行的任務。

  2. 僅在組態動作內變更目前的任務。
    由於任務組態動作現在可以立即、稍後或永遠不會執行,變更目前的任務以外的任何內容都可能導致組建行為不確定。請考慮以下程式碼

    val check by tasks.registering
    tasks.register("verificationTask") {
        // Configure verificationTask
    
        // Run verificationTask when someone runs check
        check.get().dependsOn(this)
    }
    def check = tasks.register("check")
    tasks.register("verificationTask") { verificationTask ->
        // Configure verificationTask
    
        // Run verificationTask when someone runs check
        check.get().dependsOn verificationTask
    }

    執行 gradle check 任務應執行 verificationTask,但在此範例中不會執行。這是因為 verificationTaskcheck 之間的相依性僅在 verificationTask 實現時才會發生。為避免此類問題,您只能修改與組態動作相關聯的任務。其他任務應在其自己的組態動作中修改

    val check by tasks.registering
    val verificationTask by tasks.registering {
        // Configure verificationTask
    }
    check {
        dependsOn(verificationTask)
    }
    def check = tasks.register("check")
    def verificationTask = tasks.register("verificationTask") {
        // Configure verificationTask
    }
    check.configure {
        dependsOn verificationTask
    }

    未來,Gradle 會將此類反模式視為錯誤並產生例外狀況。

  3. 偏好小幅增量變更。
    較小的變更較容易進行健全檢查。如果您中斷了組建邏輯,分析自上次成功驗證以來的變更日誌會更容易。

  4. 確保建立一個良好的驗證組建邏輯計畫。
    通常,一個簡單的 build 任務呼叫就足以驗證您的組建邏輯。不過,有些組建可能需要額外的驗證 - 了解組建的行為並確保您有一個良好的驗證計畫。

  5. 偏好自動測試而非手動測試。
    使用 TestKit 為您的組建邏輯撰寫整合測試是一種良好的做法。

  6. 避免以名稱參照任務。
    通常,以名稱參照任務是一種脆弱的模式,應避免使用。儘管任務名稱在 TaskProvider 上可用,但應盡力使用強類型模型中的參照。

  7. 盡可能使用新的任務 API。
    急切實現某些任務可能會導致其他任務的連鎖實現。使用 TaskProvider 有助於建立間接關係,以防範遞移實現。

  8. 如果您嘗試從新 API 的組態區塊存取某些 API,這些 API 可能會被禁止。
    例如,Project.afterEvaluate() 無法在組態使用新 API 註冊的任務時呼叫。由於 afterEvaluate 用於延遲組態 Project,將延遲組態與新 API 混用可能會導致難以診斷的錯誤,因為使用新 API 註冊的任務並不總是組態的,但 afterEvaluate 區塊可能總是會執行。

遷移步驟

遷移程序的第一部分是逐步檢閱程式碼,並手動將急切任務建立和組態遷移到使用組態迴避 API。

  1. 遷移影響所有任務 (tasks.all {}) 或依類型進行子集遷移 (tasks.withType(…​) {}) 的任務組態。
    這將導致您的建置急切建立較少的由外掛程式註冊的任務。

  2. 遷移由名稱組態的任務。
    這將導致您的建置急切建立較少的由外掛程式註冊的任務。例如,使用 TaskContainer#getByName(String, Closure) 的邏輯應轉換為 TaskContainer#named(String, Action)。這也包括 透過 DSL 區塊進行任務組態

  3. 將任務建立遷移到 register(…​)
    此時,您應變更任何任務建立 (使用 create(…​) 或類似方法) 改用註冊。

在進行這些變更後,您應會看到組態時間急切建立的任務數量有所改善。

遷移疑難排解

  • 正在實現哪些任務? 使用 建置掃描 透過下列步驟進行疑難排解

    1. 使用 --scan 旗標執行 Gradle 指令。

    2. 導覽至組態效能索引標籤

      taskConfigurationAvoidance navigate to performance
    3. 將顯示所有必要的資訊

      taskConfigurationAvoidance performance annotated
      1. 當建立或未建立每個任務時存在的總任務數。

        • 立即建立 代表使用急切任務 API 建立的任務。

        • 在組態期間建立 代表使用組態迴避 API 建立的任務,但已明確 (透過 TaskProvider#get()) 或隱含地使用急切任務查詢 API 實現。

        • 立即建立在組態期間建立 數字都被視為「不良」數字,應盡可能將其減至最低。

        • 在任務執行期間建立 代表在建立執行任務圖表之後建立的任務。此時建立的任何任務都不會作為圖表的一部分執行。理想情況下,此數字應為零。

        • 在任務圖表計算期間建立 代表在建立執行任務圖表時建立的任務。理想情況下,此數字應等於執行的任務數。

        • 未建立 代表在此建置階段中避免的任務。理想情況下,此數字應盡可能大。

      2. 下一節有助於回答任務在哪裡實現的問題。對於每個腳本、外掛程式或生命週期回呼,最後一欄代表立即或在組態期間建立的任務。理想情況下,此欄應為空。

      3. 專注於腳本、外掛或生命週期回呼,將會顯示所建立任務的細目。

移轉陷阱

  • 注意隱藏的急切任務實現。有許多方法可以急切配置任務。
    例如,使用任務名稱和 DSL 程式區塊來配置任務,將會立即建立任務(使用 Groovy DSL 時)

    // Given a task lazily created with
    tasks.register("someTask")
    
    // Some time later, the task is configured using a DSL block
    someTask {
        // This causes the task to be created and this configuration to be executed immediately
    }

    改用 named() 方法來取得任務參考並進行配置

    tasks.named("someTask") {
        // ...
        // Beware of the pitfalls here
    }

    類似地,Gradle 有語法糖,允許透過名稱來參照任務,而無需明確的查詢方法。這也會導致立即建立任務

    tasks.register("someTask")
    
    // Sometime later, an eager task is configured like
    task anEagerTask {
        // The following will cause "someTask" to be looked up and immediately created
        dependsOn someTask
    }

    有幾種方法可以避免這種過早建立

    • 使用 TaskProvider 變數。當在同一個建置腳本中多次參照任務時很有用。

      val someTask by tasks.registering
      
      task("anEagerTask") {
          dependsOn(someTask)
      }
      def someTask = tasks.register("someTask")
      
      task anEagerTask {
          dependsOn someTask
      }
    • 將使用者任務移轉到新的 API。

      tasks.register("someTask")
      
      tasks.register("anEagerTask") {
          dependsOn someTask
      }
    • 延遲查詢任務。當任務不是由同一個外掛建立時很有用。

      tasks.register("someTask")
      
      task("anEagerTask") {
          dependsOn(tasks.named("someTask"))
      }
      tasks.register("someTask")
      
      task anEagerTask {
          dependsOn tasks.named("someTask")
      }

要使用的延遲 API

API 備註

傳回 TaskProvider,而不是 Task

傳回 TaskProvider,而不是 Task

可以使用。如果串連 withType().getByName(),請改用 TaskCollection.named()

傳回 void,因此無法串連。

要避免的急切 API

API 備註

task myTask(type: MyTask) {}

不要使用簡寫符號。改用 register()

改用 register()

不要使用。

不要使用。

避免呼叫此方法。行為可能會在未來變更。

改用 named()

改用 named()

請改用 DomainObjectCollection.configureEach()

如果您是根據名稱進行比對,請改用 named(),它會延遲執行。matching() 需要建立所有任務,因此請試著透過限制任務類型來限制影響範圍,例如 withType().matching()

改用 named()

請改用 withType().configureEach()

請改用 configureEach()

請改用 configureEach()

避免呼叫此方法。在大部分情況下,matching()configureEach() 會更合適。

請勿使用。named() 是最接近的等效函式,但如果任務不存在,它會失敗。

iterator() 或對 Task 集合進行隱式反覆運算

避免執行此操作,因為它需要建立和設定所有任務。

remove()

避免呼叫此方法。行為可能會在未來變更。