I noticed that the quite often problem I face when I work with Gradle - is tasks ordering (either existing or my custom ones). Apparently my build works better when my tasks are executed at the right moment of the build process :)android
我注意到我在使用Gradle的時候遇到的大多數問題都是和task的執行順序有關的。很明顯若是個人構建會工做的更好若是個人task都是在正確的時候執行。bash
So let's dig deeper into how can we change tasks execution order.app
下面咱們就深刻了解一下如何更改task的執行順序。ide
I believe the most obvious way of telling your task to execute after some other task - is to use dependsOn
method.單元測試
我認爲最直接的方式來講明的你task的執行時依賴別的task的方法就是使用dependsOn方法。 測試
Let's consider existing task A
and we need to add task B
which executes only after task A
is executed:gradle
好比下面的場景,已經存在task A,咱們要添加一個task B,它的執行必需要在A執行完以後: ui
This is probably the easiest thing you can do. Given that tasks A
and B
are already defined:this
這是一個很簡單的場景,假定A和B的定義以下:.net
task A << {println 'Hello from A'} task B << {println 'Hello from B'}
What you need to do - is just tell Gradle that task B
depends on task A
B.dependsOn A
只須要簡單的調用B.dependsOn A,就能夠了。
This means that whenever I try to execute task B
- Gradle will take care of executing task A
as well:
這意味着,只要我執行task B,task A都會先執行。
$ gradle B :A Hello from A :B Hello from B
Alternatively, you could declare such a dependency right inside task configuration section:
另外,你也能夠在task的配置區中來聲明它的依賴:
task A << {println 'Hello from A'} task B { dependsOn A doLast { println 'Hello from B' } }
Result is the same.
But what if we want to insert our task inside already existing task graph?
若是咱們想要在已經存在的task依賴中插入咱們的task該怎麼作呢?
The process is pretty much the same:
過程和剛纔相似。
original task graph:
task A << {println 'Hello from A'} task B << {println 'Hello from B'} task C << {println 'Hello from C'} B.dependsOn A C.dependsOn B
our new custom task:
task B1 << {println 'Hello from B1'} B1.dependsOn B C.dependsOn B1
output:
$ gradle C :A Hello from A :B Hello from B :B1 Hello from B1 :C Hello from C
Please note, that dependsOn adds task to the set of dependencies. Thus it is totally fine to be dependent on multiple tasks:
注意dependsOn把task添加到依賴的集合中,因此依賴多個task是沒有問題的
task B1 << {println 'Hello from B1'} B1.dependsOn B B1.dependsOn Q
output:
$ gradle B1 :A Hello from A :B Hello from B :Q Hello from Q :B1 Hello from B1
Now imagine that our task depends on 2 other tasks. For this example I decided to use more real-life case. Imagine I have one task for unit tests and another for UI tests. Also I have a task which executes both unit & UI tests:
如今假定我又一個task,它依賴於其餘兩個task。這裏我使用一個真實的場景,我有兩個task,一個單元測試的task,一個是UI測試的task。另外還有一個task是跑全部的測試的,它依賴於前面的兩個task。
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} tests.dependsOn unit tests.dependsOn ui
output:
$ gradle tests :ui Hello from UI tests :unit Hello from unit tests :tests Hello from all tests!
Even though tasks unit
and UI
tests will be executed beforetask tests
, the order of execution for tasks ui
and unit
is not determined. Right now I believe they will be executed in alphabetical order, but this behavior is an implementation detail and you definitely should not rely on this fact.
儘管unitest和UI test會子啊test task以前執行,可是unit和ui這兩個task的執行順序是不能保證的。雖然如今來看是按照字母表的順序執行,但這是依賴於Gradle的實現的,你的代碼中絕對不能依賴這種順序
Since UI tests are executing much longer than unit tests, I want my unit tests run first and only if everything OK - proceed to executing UI tests. So what should I do if I want my unit tests run before UI tests?
因爲UI測試時間遠比unit test時間長,所以我但願unit test先執行。
One way for solving this would be to make UI test task depend on unit test task:
一個解決辦法就是讓ui task依賴於unit task:
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} tests.dependsOn unit tests.dependsOn ui ui.dependsOn unit // <-- I added this dependency
output
paveldudka$ gradle tests :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests!
Now my unit tests are getting executed before UI tests! Great!
如今unit test會在ui test以前執行了。
BUT! There is one really big fat nasty problem with this approach! My UI tests do not really depend on unit tests. I wanna be able to run my UI tests separately, but now every time I want to run my UI tests - my unit tests will be run as well!
可是這裏有個很噁心的問題,個人ui測試其實並不依賴於unit test。我但願可以單獨的執行ui test,可是這裏每次我執行ui test,都會先執行unit test。
That's where mustRunAfter
method comes into play. It tells Gradle to run task after task specified as an argument. So essentially, we do not introduce dependency between our unit tests and UI tests, but instead we told Gradle to give unit tests priority if they are executed together, so unit tests are executedbefore our UI test suite:
這裏就要用到mustRunAfter了。mustRunAfter並不會添加依賴,它只是告訴Gradle執行的優先級若是兩個task同時存在。好比咱們這裏就能夠指定ui.mustRunAfter unit,這樣若是ui task和unit task同時存在,Gradle會先執行unit test,而若是隻執行gradle ui,並不會去執行unit task。
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} tests.dependsOn unit tests.dependsOn ui ui.mustRunAfter unit
output
paveldudka$ gradle tests :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests!
And the dependency graph looks like:
依賴關係以下圖:
Notice that we lost explicit dependency between UI tests and unit tests! Now if I decide to run just UI tests - my unit tests won't be executed.
Please note that
mustRunAfter
is marked as "incubating" (as of Gradle 2.4) which means that this is an experimental feature and its behavior can be changed in future releases.(mustRunAfter在Gradle2.4中目前仍是實驗性的功能。)
Now I have task which runs both UI and unit tests. Great! Let's say each of them produces test report. So I decided to create a task which merges 2 test reports into one:
如今咱們已經有兩個task,unit和ui,假定這兩個task都會輸出測試報告,如今我想把這兩個測試報告合併成一個:
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} task mergeReports << {println 'Merging test reports'} tests.dependsOn unit tests.dependsOn ui ui.mustRunAfter unit mergeReports.dependsOn tests
Now if I want to get test report with both UI & unit tests - I execute mergeReports
task:
如今若是我想得到ui和unit的測試報告,執行task mergeReports就能夠了。
$ gradle mergeReports :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests! :mergeReports Merging test reports
It works, but... it looks sloppy.. mergeReports
task doesn't make a lot of sense from user (by user I mean developer :) ) perspective. I want to be able to execute tests
and get merged report. Obviously, I could add merge logic inside tests
task, but for the sake of this demo - I want to keep this logic in separate mergeReports
task.
這個task是能工做,可是看起來好笨啊。mergeReports從用戶的角度來看感受不是特別好。我但願執行tests task就能夠得到測試報告,而沒必要知道mergeReports的存在。固然我能夠把merge的邏輯挪到tests task中,但我不想把tests task搞的太臃腫,我仍是繼續把merge的邏輯放在mergeReports task中。
finalizedBy method come to the rescue. Its name is quite self-explanatory - it adds finalizer task to this task.
finalizeBy來救場了。顧名思義,finalizeBy就是在task執行完以後要執行的task。
So let's modify our script as follows:
修改咱們的腳本以下:
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} task mergeReports << {println 'Merging test reports'} tests.dependsOn unit tests.dependsOn ui ui.mustRunAfter unit mergeReports.dependsOn tests tests.finalizedBy mergeReports
Now I'm able to execute tests
task and I still get my merged test report:
如今執行tests task就能夠拿到測試報告了:
$ gradle tests :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests! :mergeReports Merging test reports
Please note that
finalizedBy
is marked as "incubating" (as of Gradle 2.4) which means that this is an experimental feature and its behavior can be changed in future releases.(注意,finalizedBy也是Gradle2.4的實驗性功能)
This is pretty much it - with these 3 tools you can easily tune your build process!
Happy gradling!