tugas
Kode berikut menunjukkan tiga tugas lulusan, dan kami akan menjelaskan perbedaan antara ketiga ini nanti.
Tugas mytask {println "Halo, dunia!" } tugas mytask {dolast {println "halo, dunia!" }} tugas myTask << {println "Halo, dunia!" }Tujuan saya adalah membuat tugas yang mencetak "halo, dunia!" Saat itu dieksekusi. Ketika saya pertama kali membuat tugas, saya kira itu harus ditulis seperti ini:
Tugas mytask {println "Halo, dunia!" }Sekarang, cobalah untuk menjalankan myTask ini, masukkan Gradle MyTask pada baris perintah, dan cetak sebagai berikut:
Pengguna $ Gradle MyTask Halo, Dunia! : MyTask terkini
Tugas ini sepertinya berhasil. Itu mencetak "Halo, dunia!".
Namun, sebenarnya bukan yang kami harapkan. Mari kita lihat alasannya. Masukkan tugas Gradle pada baris perintah untuk melihat semua tugas yang tersedia.
Pengguna $ Gradle Tugas Halo, Dunia! : Tugas ------------------------------------------------------------ Semua tugas yang dapat dijalankan dari Root Project ------------------------------------------------------------ Bangun Tugas Pengaturan ----------------- Init-Menginisialisasi Build Gradle baru. [Inkubasi] ..........
Tunggu, mengapa "Halo, Dunia!" dicetak? Saya hanya ingin melihat tugas apa yang tersedia dan tidak menjalankan tugas khusus!
Alasannya sebenarnya sangat sederhana. Gradle Task memiliki dua tahap utama dalam siklus hidupnya: tahap konfigurasi dan tahap eksekusi.
Mungkin kata -kata saya tidak terlalu akurat, tetapi ini benar -benar dapat membantu saya memahami tugas.
Gradle harus mengkonfigurasi tugas sebelum menjalankannya. Maka pertanyaannya adalah, bagaimana saya tahu kode mana dalam tugas saya dieksekusi selama proses konfigurasi dan kode mana yang dijalankan saat tugas dijalankan? Jawabannya adalah bahwa kode di tingkat atas tugas adalah kode konfigurasi, seperti:
Tugas mytask {def name = "pavel" // <- baris kode ini akan mengeksekusi println "halo, dunia!" //// <- baris kode ini juga akan dijalankan pada tahap konfigurasi}Inilah sebabnya ketika saya menjalankan tugas Gradle, saya mencetak "Halo, Dunia!" - Karena kode konfigurasi dijalankan. Tapi ini bukan efek yang saya inginkan, saya ingin "Halo, dunia!" untuk mencetaknya hanya ketika saya secara eksplisit memanggil myTask. Untuk mencapai efek ini, cara termudah adalah dengan menggunakan metode tugas#dolast ().
tugas mytask {def text = 'halo, dunia!' // Konfigurasikan tugas saya dolast {println text // Ini dieksekusi saat tugas saya dipanggil}}Sekarang, "Halo, dunia!" hanya akan mencetak saat saya mengeksekusi lulusan myTask. Keren, sekarang saya tahu cara mengkonfigurasi dan membuat tugas melakukan hal yang benar. Ada pertanyaan lain. Dalam contoh awal, apa arti << simbol tugas ketiga?
Tugas myTask2 << {println "Halo, dunia!" }Ini sebenarnya hanya versi gula sintaksis dari Dolast. Ini memiliki efek yang sama dengan metode penulisan berikut:
Tugas mytask {dolast {println 'halo, dunia!' // Ini dieksekusi saat tugas saya dipanggil}}Namun, semua kode dengan cara ini dijalankan dan tidak ada bagian konfigurasi dari kode, sehingga mereka lebih cocok untuk tugas -tugas sederhana yang tidak memerlukan konfigurasi. Setelah tugas Anda perlu dikonfigurasi, Anda masih perlu menggunakan versi Dolast.
tata bahasa
Script Gradle ditulis dalam bahasa yang asyik. Sintaks Groovy agak seperti Java, saya harap Anda bisa menerimanya.
Jika Anda sudah terbiasa dengan Groovy, Anda dapat melewatkan bagian ini.
Ada konsep yang sangat penting dalam groovy yang Anda butuhkan untuk memahami penutupan (penutupan)
Penutupan
Penutupan adalah kunci pemahaman kita tentang Gradle. Penutupan adalah blok kode terpisah yang dapat menerima parameter, mengembalikan nilai, atau ditetapkan ke variabel. Ini mirip dengan antarmuka yang dapat dipanggil di java dan masa depan, dan juga seperti penunjuk fungsi, yang mudah dimengerti. . .
Kuncinya adalah bahwa kode ini akan dieksekusi saat Anda menyebutnya, bukan saat dibuat. Lihat contoh penutupan:
def myclosure = {println 'hello world!' } // Jalankan ClosingClosure () kami#Output: Halo Dunia!Berikut adalah penutupan yang menerima parameter:
def myclosure = {string str string -> println str} // Jalankan closingclosure kami ('halo world!')#output: halo dunia!Jika penutupan hanya menerima satu parameter, Anda dapat menggunakannya untuk merujuk parameter ini:
def myclosure = {println it} // Jalankan closingclosure kami ('halo world!')#output: halo dunia!Penutupan yang menerima beberapa parameter:
def myclosure = {string str, int num -> println "$ str: $ num"} // jalan renovationyclosure kami ('my string', 21) #Output: my string: 21Selain itu, jenis parameter adalah opsional, dan contoh di atas dapat disingkat sebagai berikut:
def myclosure = {str, num -> println "$ str: $ num"} // jalankan closingclosure kami ('my string', 21) #Output: my string: 21Yang keren adalah bahwa variabel dari konteks saat ini dapat digunakan dalam penutupan. Secara default, konteks saat ini adalah kelas tempat penutupan dibuat:
def myvar = 'hello world!' def myclosure = {println myvar} myclosure ()#output: hello world!Poin keren lainnya adalah bahwa konteks penutupan dapat diubah, melalui penutupan#setDelegate (). Fitur ini sangat berguna:
def myclosure = {println myvar} // Saya mereferensikan myvar dari myclass classmyclass m = new myclass () myclosure.setDelegate (m) myclosure () class myclass {def myvar = 'halo dari myclass!'}#output: halo dari myclass {def myvar = 'hello from myclass!'}#output: halo dari myclass saya!Seperti yang Anda lihat, MyVar tidak ada saat membuat penutupan. Tidak ada masalah dengan ini karena ketika kita melakukan penutupan, MyVar ada dalam konteks penutupan. Dalam contoh ini. Karena saya mengubah konteksnya menjadi m sebelum melakukan penutupan, MyVar ada.
Lulus penutupan sebagai parameter
Keuntungan dari penutupan adalah bahwa ia dapat diteruskan ke metode yang berbeda, yang dapat membantu kita memisahkan logika eksekusi. Dalam contoh sebelumnya saya telah menunjukkan cara meneruskan penutupan ke contoh kelas. Di bawah ini kita akan melihat berbagai metode yang menerima penutupan sebagai parameter:
1. Hanya menerima satu parameter dan parameter adalah metode penutupan: mymethod (myclosure)
2. Jika metode ini hanya menerima satu parameter, tanda kurung dapat dihilangkan: mymethod myclosure
3. Anda dapat menggunakan penutupan inline: mymethod {println 'hello world'}
4. Metode untuk menerima dua parameter: mymethod (arg1, myclosure)
5. Mirip dengan 4, penutupan tunggal adalah inline: mymethod (arg1, {println 'hello world'})
6. Jika parameter terakhir adalah penutupan, itu dapat dikeluarkan dari tanda kurung: mymethod (arg1) {println 'hello world'}
Di sini saya hanya ingin mengingatkan Anda apakah penulisan 3 dan 6 terlihat akrab?
Contoh Lulusan
Sekarang kita telah memahami sintaks dasar, bagaimana kita menggunakannya dalam skrip gradle? Mari kita lihat contoh berikut:
BuildScript {repositories {jCenter ()} dependensi {classpath 'com.android.tools.build:gradle:1.2.3'}} AllProjects {repositories {jCenter ()}} Setelah Anda mengetahui sintaks Groovy, apakah mudah untuk memahami contoh di atas?
Pertama adalah metode buildscript, yang menerima penutupan:
Def Buildscript (penutupan penutupan)
Berikutnya adalah metode AllProjects, yang juga menerima parameter penutupan:
def allprojects (penutupan penutupan)
Yang lain serupa. . .
Tampaknya jauh lebih mudah sekarang, tetapi saya tidak mengerti satu hal, yaitu, di mana metode ini didefinisikan? Jawabannya adalah Proyek
Proyek
Ini adalah kunci untuk memahami skrip gradle.
Pernyataan blok pada tingkat atas skrip build akan didelegasikan ke instance proyek, yang menunjukkan bahwa proyek adalah tempat yang saya cari.
Mencari metode buildscript pada halaman dokumen proyek akan menemukan blok skrip buildscript {} (blok skrip). dll. Apa itu blok skrip? Menurut dokumentasi:
Blok skrip adalah metode yang hanya menerima penutupan sebagai parameter. Terus baca dokumentasi buildscript. Dokumen itu mengatakan bahwa delegasi untuk: Scripthandler dari buildscript. Artinya, kami memberikan penutupan ke metode buildscript, dan konteks eksekusi akhir adalah ScripThandler. Dalam contoh di atas, penutupan kami diteruskan ke Buildscript memanggil metode repositori (penutupan) dan dependensi (penutupan). Karena penutupan telah dipercayakan kepada Scripthandler, maka kami akan mencari metode dependensi di ScripThandler.
Void dependensi (penutupan konfigurasi) ditemukan. Menurut dokumentasi, dependensi digunakan untuk mengonfigurasi dependensi skrip. Dan ketergantungan akhirnya dipercayakan kepada DependencyHandler.
Saya melihat betapa banyaknya lulusan yang digunakan. Memahami penghargaan itu sangat penting.
Blok skrip
Secara default, banyak blok skrip telah ditentukan sebelumnya dalam proyek, tetapi plugin Gradle memungkinkan kita untuk mendefinisikan blok skrip baru sendiri!
Ini berarti bahwa jika Anda memposting beberapa {...} di tingkat atas skrip build, tetapi Anda tidak dapat menemukan blok atau metode skrip ini dalam dokumentasi Gradle, dalam kebanyakan kasus, ini adalah beberapa blok skrip yang ditentukan dalam plugin.
Blok skrip Android
Mari kita lihat file android default/build.gradle file:
apply plugin: 'com.android.application'android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.trickyandroid.testapp" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile ('proguard-android.txt'), 'proguard-rules.pro'}}}Urutan tugas
Saya perhatikan bahwa sebagian besar masalah yang saya temui ketika menggunakan Gradle terkait dengan urutan eksekusi tugas. Jelas jika bangunan saya akan bekerja lebih baik jika tugas saya dieksekusi pada waktu yang tepat. Mari kita lihat lebih dekat bagaimana mengubah urutan eksekusi tugas.
Dependson
Saya pikir cara paling langsung untuk menjelaskan cara Anda mengandalkan tugas -tugas lain saat menjalankan tugas Anda adalah dengan menggunakan metode Dependson.
Misalnya, dalam skenario berikut, tugas A sudah ada. Kami ingin menambahkan tugas B, dan pelaksanaannya harus setelah dijalankan:
Ini adalah skenario yang sangat sederhana, dengan asumsi bahwa definisi A dan B adalah sebagai berikut:
tugas a << {println 'halo dari'} tugas b << {println 'halo dari b'} Cukup hubungi B.Dependentson A dan tidak apa -apa.
Ini berarti bahwa selama saya menjalankan tugas B, tugas A akan dijalankan terlebih dahulu.
Paveldudka $ Gradle B: Ahello dari A: Bhello dari B
Selain itu, Anda dapat mendeklarasikan ketergantungannya di area konfigurasi tugas:
tugas a << {println 'halo dari'} tugas b {dependson a dolast {println 'hello dari b'}} Bagaimana jika kita ingin memasukkan tugas kita ke dalam ketergantungan tugas yang ada?
Prosesnya mirip dengan apa yang sekarang. Asumsikan bahwa ketergantungan tugas berikut sudah ada:
tugas a << {println 'halo dari'} tugas b << {println 'halo dari b'} tugas c << {println 'hello dari c'} b.dependentson Ac.dependentson b bBergabunglah dengan tugas baru kami
Tugas B1 << {println 'halo dari b1'} b1.dependson bc.dependson b1Keluaran:
Paveldudka $ Gradle C: Ahello dari A: Bhello dari B: B1Hello dari B1: Chello dari C
Perhatikan bahwa Dependson menambahkan tugas ke koleksi dependen, jadi tidak masalah untuk mengandalkan beberapa tugas.
tugas b1 << {println 'hello dari b1'} b1.dependson bb1.dependson qKeluaran:
paveldudka $ gradle b1: ahello dari a: bhello dari b: qhello dari q: b1hello dari b1
Mustrunafter
Sekarang anggaplah saya punya tugas lain, yang tergantung pada dua tugas lainnya. Di sini saya menggunakan skenario nyata di mana saya memiliki dua tugas, satu tugas yang diuji dan satu tugas yang diuji UI. Ada juga tugas yang menjalankan semua tes, yang tergantung pada dua tugas sebelumnya.
unit tugas << {println 'halo dari tes unit'} tugas Ui << {println 'halo dari tes UI'} tes tugas << {println 'halo dari semua tes!'} tests.dependson unittests.dependson uiKeluaran:
Tes Paveldudka $ Gradle: Uihello dari UI Tes: Unithello dari Unit Tes: testshello dari semua tes!
Meskipun uji Unitest dan UI akan dijalankan sebelum tugas pengujian, urutan eksekusi unit dan UI tidak dapat dijamin. Meskipun dieksekusi dalam urutan alfabet sekarang, ini tergantung pada implementasi Gradle, dan Anda tidak boleh bergantung pada urutan ini dalam kode Anda.
Karena waktu tes UI jauh lebih lama dari waktu tes unit, saya ingin tes unit dieksekusi terlebih dahulu. Salah satu solusi adalah membuat tugas UI tergantung pada tugas unit.
unit tugas << {println 'halo dari tes unit'} tugas Ui << {println 'halo dari tes UI'} tes tugas << {println 'halo dari semua tes!'} tests.dependson unittests.dependson uiui.dependson unit // <- Saya menambahkan ketergantungan ini iniKeluaran:
Paveldudka $ Gradle Tes: Unithello dari Tes Unit: Uihello dari UI Tes: testshello dari semua tes!
Sekarang tes unit akan dieksekusi sebelum tes UI.
Tapi ada masalah yang sangat menjijikkan di sini. Tes UI saya sebenarnya tidak bergantung pada tes unit. Saya berharap dapat menjalankan tes UI secara terpisah, tetapi di sini setiap kali saya menjalankan tes UI, saya akan menjalankan tes unit terlebih dahulu.
Mustrunafter diperlukan di sini. Mustrunafter tidak menambah dependensi, itu hanya memberi tahu Gradle prioritas eksekusi jika dua tugas ada pada saat yang sama. Misalnya, kami dapat menentukan unit UI.Mustrunafter di sini. Dengan cara ini, jika tugas UI dan tugas unit ada pada saat yang sama, Gradle pertama -tama akan menjalankan tes unit, dan jika hanya lulusan UI yang dieksekusi, tugas unit tidak akan dieksekusi.
unit tugas << {println 'halo dari tes unit'} tugas Ui << {println 'halo dari tes UI'} tes tugas << {println 'halo dari semua tes!'} tests.dependson unittests.dependson uiui.mustrunafter Unit unitKeluaran:
Paveldudka $ Gradle Tes: Unithello dari Tes Unit: Uihello dari UI Tes: testshello dari semua tes!
Hubungan ketergantungan adalah sebagai berikut:
Mustrunafter saat ini merupakan fitur eksperimental di Gradle 2.4.
finalisasi
Sekarang kami memiliki dua tugas, unit dan UI, dengan asumsi bahwa kedua tugas akan menghasilkan laporan tes, sekarang saya ingin menggabungkan dua laporan tes ini menjadi satu:
unit tugas << {println 'halo dari tes unit'} tugas Ui << {println 'halo dari tes UI'} tes tugas << {println 'halo dari semua tes!'} tugas mergeReports << {println 'Laporan uji gabungan'} tests.dependson unittests.dependonpendson.dependson.Sekarang jika saya ingin mendapatkan laporan tes untuk UI dan unit, jalankan tugas tugas.
paveldudka $ gradle mergereports: unithello dari unit tes: uihello dari tes UI: testshello dari semua tes!: laporan tes mergereportsmerging
Tugas ini berhasil, tetapi terlihat sangat bodoh. Mergereports tidak terasa sangat baik dari perspektif pengguna. Saya ingin menjalankan tugas tes untuk mendapatkan laporan tes tanpa harus mengetahui keberadaan mergereports. Tentu saja saya dapat memindahkan logika gabungan ke tugas tes, tetapi saya tidak ingin membuat tugas tes terlalu membengkak, jadi saya akan terus memasukkan logika gabungan ke dalam tugas mergereports.
Finalizeby ada di sini untuk menyimpan adegan. Seperti namanya, FinizeBeby adalah tugas yang akan dieksekusi setelah tugas dieksekusi. Ubah skrip kami sebagai berikut:
unit tugas << {println 'halo dari tes unit'} tugas Ui << {println 'halo dari tes UI'} tes tugas << {println 'halo dari semua tes!'} tugas mergereports << {println 'laporan tes gabungan'} tests.dependson unittests.dependson.dependson. tests. finalizedby mergereportsSekarang jalankan tugas tes dan Anda bisa mendapatkan laporan tes:
Tes Paveldudka $ Gradle: Unithello dari Tes Unit: Uihello dari UI Tes: testshello dari semua tes !: Laporan Uji Mergereportsmerging