Gradle Task Configuration Avoidance

這個章節介紹了處理任務時"避免配置",並且解釋了遷移構建以有效實用配置避免api的一些指導原則,這裏描述的API與現有的API共存,但是現有的API將在以後的幾個重要版本中被代替。在Gradle 5.1中,我們建議使用配置避免的API來創建自定義的Plugins。

配置避免API是如何工作的?

nutshell中,該API允許構建避免在Gradle的配置階段創建和配置任務的成本,而這些任務永遠不會被執行。例如,當跑一個編譯的任務,其他像代碼質量,測試和發版的不相關的任務將不會被執行,所以任何花費在創建和配置上的任務都是不必要的。配置避免API避免配置任務,如果這些任務在在構建過程中是不被需要的,這些任務會對整個構建時間產生巨大的影響。

爲了避免創建和配置任務,我們使用"被註冊的"而不是被創建的。當一個任務在這種狀態下,這個任務時被標記爲可以構建和配置的,並且引用可以通過這個任務,但是這個任務本身並不會真正的被創建,並且配置項目不會被執行。這個任務將一直保持這種狀態直到在編譯過程中需要實例化這個任務對象。如果這個任務永遠不被需要,任務將一直保持註冊的狀態,那麼這個任務的創建和配置的時間將被節省。

在Gradle中,你可以用TaskContainer.register(java.lang.String)來註冊任務。此方法有多種變體,允許提供用於修改任務配置的任務類型和/或操作。方法register(...)返回的是一個TaskProvider,而不是一個Task類型,TaskProvider類型可以用在所有Task可以用的地方。

指導

如何延遲一個任務的創建

有效的配置避免任務要求構建者將TaskContainer.create(java.lang.String)變爲TaskContainer.register(java.lang.String)。老版本的Gradle僅支持create(...)接口請求,應該避免使用這個API。單獨使用register(...)可能不能完全避免任務配置。可能需要改變其他的代碼,以下是解釋。

如何延遲任務配置

DomainObjectCollection.all(org.gradle.api.Action)DomainObjectCollection.withType(java.lang.Class,org.gradle.api.Action)將立即創建和配置任何註冊過的任務。爲了延遲任務配置,你需要遷移配置API,可以看最後的表格。

如何引用一個沒有創建/配置的任務

可以通過TaskContainer.register(java.lang.String)或者使用TaskCollection.named(java.lang.String)這兩個方法來獲取。
使用Provider.get()或者TaskCollection.getByName(java.lang.String)會創建或者配置該task。函數像Task.dependsOn(java.lang.Object...)ConfigurableFileCollection.builtBy(java.lang.Object...)與任務的工作機制是一樣的。
如果你想使用名稱來配置一個任務,你將需要使用等效的配置避免。可以看最下面的表格。

如何獲取任務的實例?

如果你熱灸想獲取任務的實例,你需要使用TaskCollection.named(java.lang.String)Provicer.get()。使用這兩個方法,將會觸發創建和配置任務流程。

遷移指導

接下來將介紹一些我們在遷移過程中需要遵循的一般準則,和一些建議。我們還將介紹一些故障排除和陷阱,以幫助您解決遷移過程中可能遇到的一些問題。

普遍準則

  1. 使用help任務作爲一個基準在遷移的過程中。help任務時一個完美的候選基準對於你的遷移過程。
  2. 僅在配置操作中更改當前任務。因爲任務配置操作現在可以立即、稍後或從不運行,所以修改當前任務之外的任何內容都可能導致構建中出現不確定的行爲。考慮以下代碼:
val check by tasks.registering
tasks.register("verificationTask") {
    // Configure verificationTask

    // Run verificationTask when someone runs check
    check.get().dependsOn(this)
}

執行gradle檢查任務應該執行verificationTask,但是在這個例子中,它不會執行。這是因爲verificationTaskcheck之間的依賴關係只在verificationTask實現時纔會發生。爲了避免此類問題,您必須只修改與配置操作關聯的任務。其他任務應該在它們自己的配置操作中進行修改。代碼將變成:

val check by tasks.registering
val verificationTask by tasks.registering {
    // Configure verificationTask
}
check {
    dependsOn(verificationTask)
}

將來,Gradle將會考慮這種錯誤,並且拋出一個異常。

  1. 喜歡小的增量變更。較小的更改更容易進行完整性檢查。如果您破壞了構建邏輯,那麼分析上一次成功驗證以來的變更日誌將會更加容易。
  2. 確保建立了驗證構建邏輯的良好計劃。通常,一個簡單的構建任務調用就可以驗證您的構建邏輯。然而,有些構建可能需要額外的驗證——理解構建的行爲並確保您有一個好的驗證計劃。
  3. 與手工測試相比,更喜歡自動測試。使用TestKit爲構建邏輯編寫集成測試是一個很好的實踐。
  4. 避免按名稱引用任務。在大多數情況下,按名稱引用任務是一種脆弱的模式,應該避免。雖然任務名稱在TaskProvider上可用,但是應該儘量使用強類型模型中的引用。
  5. 儘可能多地使用新的任務API。急於完成某些任務可能會導致其他任務的級聯。使用TaskProvider可以幫助創建一個間接性實現,以防止傳遞性實現。
  6. 如果您試圖從新API的配置塊中訪問某些API,則可能會禁用它們。例如,在配置用新API註冊的任務時不能調用Project.afterEvaluate()。由於afterEvaluate用於延遲配置項目,將延遲配置與新API混合使用可能會導致難以診斷的錯誤,因爲並不總是配置用新API註冊的任務,但是可能期望afterEvaluate塊總是執行

遷移步驟

遷移過程的第一部分是遍歷代碼並手動遷移迫切需要的任務創建和配置,以使用配置避免api。下面探討成功遷移的建議步驟。在執行這些步驟時,請記住上面的指導原則。

在插件中使用新的API將要求用戶使用Gradle 4.9或更高版本。插件作者應該參考支持Gradle section的舊版本。

  1. 遷移影響所有任務(任務)的任務配置。按類型(tasks.withType(…){})劃分的所有{}或子集。這將導致您的構建急於創建由插件註冊的更少的任務。
  2. 按名稱配置的i篦任務。與前一點類似,這將導致您的構建急於創建更少的由插件註冊的任務。例如,使用任務容器#getByName(String, Closure/Action)的邏輯應該轉換爲任務容器#named(String).configure(Closure/Action)。這還包括通過DSL塊進行的任務配置。
  3. 將任務創建遷移到register(…)。此時,您應該在創建任務的任何地方進行更改,以註冊這些任務。

對於以上所有步驟,請注意延遲配置的常見缺陷

在進行這些更改之後,您應該會看到在配置時急切創建的任務數量的改進。使用構建掃描來了解哪些任務仍然在急切地創建,以及在哪裏正在發生

故障排除

正在實現哪些任務?隨着我們不斷開發該特性,將提供更多的報告和故障排除信息來回答這個問題。同時,構建掃描是回答這個問題的最佳方法。遵循以下步驟:

  1. 創建構建掃描。使用--scan標誌執行Gradle命令。

  2. 導航到configuration performance選項卡。
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-s43E7ViO-1569757514464)(https://docs.gradle.org/current/userguide/img/taskConfigurationAvoidance-navigate-to-performance.png)]
    圖1所示,導航到“生成掃描”中的“配置性能”選項卡

    1. 從左側菜單導航到性能卡。
    2. 從性能卡頂部導航到configuration選項卡。
  3. 所有的信息要求被展現
    https://docs.gradle.org/current/userguide/img/taskConfigurationAvoidance-performance-annotated.png
    圖2,配置性能選項卡在構建掃描註釋

    1. 創建或不創建每個任務時顯示的總任務。
      • “立即創建”表示使用即時任務api創建的任務。
      • “在配置期間創建的”表示使用配置避免api創建的任務,但是是顯式實現的(通過TaskProvider#get())或隱式使用急切的任務查詢api。
      • “立即創建的”和“在配置期間創建的”數字都被認爲是“壞的”數字,應該儘可能地將其最小化。
      • “在任務圖計算期間創建”表示在構建執行任務圖時創建的任務。理想情況下,這個數字應該等於執行的任務數量。
      • “未創建”表示在此構建會話中避免的任務。
    2. 下一節將幫助回答任務在何處實現的問題。對於每個腳本、插件或生命週期回調,最後一列表示立即或在配置期間創建的任務。理想情況下,這一列應該是空的。
    3. 關注腳本、插件或生命週期回調將顯示創建的任務的細分。

陷阱

  • 小心隱藏的熱切的任務實現。有許多方法可以急切地配置任務。例如,使用任務名和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").configure {
    // ...
    // 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)
}
  • 遷移消費的task到新的API
tasks.register("someTask")

tasks.register("anEagerTask") {
    dependsOn someTask
}
  • 懶加載方式查找任務, 當任務不是由相同的插件創建時非常有用。
tasks.register("someTask")

task("anEagerTask") {
    dependsOn(tasks.named("someTask"))
}
  • 編譯瀏覽插件buildScanPublishPrevious任務直到1.15版本纔會啓動。升級構建掃描插件以使用最新版本。

支持舊版本的Gradle

本節描述了兩種方法,如果您必須保持與大於4.9的Gradle版本的兼容性,則可以使插件向後兼容舊版本的Gradle。大多數新的API方法都可以從Gradle 4.9開始使用。

雖然向後兼容性對用戶很好,但我們仍然建議及時升級到更新的Gradle版本。這將減少您的維護負擔。

保持兼容性的第一個方法是根據Gradle 4.9 API編譯插件,並使用Groovy有條件地調用正確的API(例子)。

第二種方法是使用Java反射來處理編譯期間api不可用的事實(例子)。

強烈建議使用TestKit和Gradle的多個版本進行跨版本測試。

  • 使用groovy.lang的方法。在新的API中,使用org.gradle.api.Action方法覆蓋閉包。
  • 未來可能會根據用戶反饋添加更多方便的方法。
  • 一些舊的API方法可能永遠無法在新的API中直接替換。
  • 在通過配置避免方法註冊的配置操作中訪問某些api時,可能會受到限制。

Table

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章