งาน
รหัสต่อไปนี้แสดงงาน Gradle สามงานและเราจะอธิบายความแตกต่างระหว่างทั้งสามนี้ในภายหลัง
งาน mytask {println "สวัสดีโลก!" } งาน mytask {dolast {println "สวัสดีโลก!" }} งาน mytask << {println "สวัสดีโลก!" -เป้าหมายของฉันคือการสร้างงานที่พิมพ์ออกมา "สวัสดีโลก!" เมื่อมันดำเนินการ เมื่อฉันสร้างงานครั้งแรกฉันเดาว่ามันควรจะเขียนเช่นนี้:
งาน mytask {println "สวัสดีโลก!" -ตอนนี้ลองดำเนินการ mytask นี้ป้อน Gradle MyTask บนบรรทัดคำสั่งและพิมพ์ดังนี้:
ผู้ใช้ $ Gradle MyTask สวัสดีโลก! : MyTask ทันสมัย
งานนี้ดูเหมือนว่ามันใช้งานได้ มันพิมพ์ "สวัสดีโลก!"
อย่างไรก็ตามมันไม่ใช่สิ่งที่เราคาดหวัง ลองมาดูกันว่าทำไม ป้อนงาน Gradle บนบรรทัดคำสั่งเพื่อดูงานที่มีอยู่ทั้งหมด
ผู้ใช้ $ Gradle Tasks สวัสดีโลก! : งาน --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [บ่ม] ..........
เดี๋ยวก่อนทำไม "สวัสดีโลก!" พิมพ์ออกมา? ฉันแค่อยากเห็นว่ามีงานอะไรและไม่ได้ทำงานที่กำหนดเอง!
เหตุผลนั้นง่ายมาก งาน Gradle มีสองขั้นตอนหลักในวงจรชีวิต: ขั้นตอนการกำหนดค่าและขั้นตอนการดำเนินการ
บางทีคำพูดของฉันไม่ถูกต้องมาก แต่สิ่งนี้สามารถช่วยให้ฉันเข้าใจงานได้จริงๆ
Gradle ต้องกำหนดค่างานก่อนที่จะดำเนินการ จากนั้นคำถามคือฉันจะรู้ได้อย่างไรว่ารหัสใดในงานของฉันถูกดำเนินการในระหว่างกระบวนการกำหนดค่าและรหัสใดที่ทำงานเมื่องานถูกดำเนินการ คำตอบคือรหัสที่ระดับสูงสุดของงานคือรหัสการกำหนดค่าเช่น:
Task MyTask {def name = "pavel" // <- รหัสบรรทัดนี้จะเรียกใช้งาน println "สวัสดีโลก!" //// <- รหัสบรรทัดนี้จะดำเนินการในขั้นตอนการกำหนดค่า}นี่คือเหตุผลที่เมื่อฉันดำเนินงาน Gradle ฉันพิมพ์ "สวัสดีโลก!" - เนื่องจากรหัสการกำหนดค่าถูกเรียกใช้งาน แต่นี่ไม่ใช่เอฟเฟกต์ที่ฉันต้องการฉันต้องการ "สวัสดีโลก!" เพื่อพิมพ์ออกมาเฉพาะเมื่อฉันเรียก MyTask อย่างชัดเจน เพื่อให้ได้เอฟเฟกต์นี้วิธีที่ง่ายที่สุดคือการใช้วิธีงาน#dolast ()
Task MyTask {def text = 'Hello, World!' // กำหนดค่างานของฉัน dolast {println text // นี่จะดำเนินการเมื่องานของฉันเรียกว่า}}ตอนนี้ "สวัสดีโลก!" จะพิมพ์ออกมาเฉพาะเมื่อฉันดำเนินการ Gradle MyTask เจ๋งตอนนี้ฉันรู้วิธีกำหนดค่าและทำให้งานทำสิ่งที่ถูกต้อง มีคำถามอื่น ในตัวอย่างเริ่มต้นสัญลักษณ์ << ของงานที่สามหมายถึงอะไร?
Task MyTask2 << {println "สวัสดีโลก!" -นี่เป็นเพียง Dolast รุ่นน้ำตาลวากยสัมพันธ์ มันมีผลเช่นเดียวกับวิธีการเขียนต่อไปนี้:
งาน mytask {dolast {println 'สวัสดีโลก!' // สิ่งนี้จะดำเนินการเมื่องานของฉันเรียกว่า}}อย่างไรก็ตามรหัสทั้งหมดด้วยวิธีนี้จะถูกดำเนินการและไม่มีส่วนการกำหนดค่าของรหัสดังนั้นจึงเหมาะสำหรับงานง่าย ๆ ที่ไม่ต้องการการกำหนดค่า เมื่อต้องกำหนดค่างานของคุณแล้วคุณยังต้องใช้เวอร์ชัน Dolast
ไวยากรณ์
สคริปต์ Gradle เขียนด้วยภาษา Groovy ไวยากรณ์ของ Groovy เป็นเหมือน Java ฉันหวังว่าคุณจะยอมรับได้
หากคุณคุ้นเคยกับ Groovy อยู่แล้วคุณสามารถข้ามส่วนนี้ได้
มีแนวคิดที่สำคัญมากใน groovy ที่คุณต้องเข้าใจการปิด (ปิด)
การปิด
การปิดเป็นกุญแจสำคัญในการทำความเข้าใจ Gradle การปิดเป็นบล็อกของรหัสแยกต่างหากที่สามารถรับพารามิเตอร์ค่าส่งคืนหรือถูกกำหนดให้กับตัวแปร มันคล้ายกับอินเทอร์เฟซ callable ใน Java และ Future และมันก็เหมือนตัวชี้ฟังก์ชั่นซึ่งเข้าใจง่าย - -
กุญแจสำคัญคือรหัสนี้จะถูกดำเนินการเมื่อคุณเรียกมันว่าไม่ใช่เมื่อมันถูกสร้างขึ้น ดูตัวอย่างการปิด:
def myclosure = {println 'Hello World!' } // ดำเนินการปิดการปิด ()#เอาท์พุท: Hello World!นี่คือการปิดที่ได้รับพารามิเตอร์:
def myclosure = {string str -> println str} // ดำเนินการปิด closingclosure ของเรา ('Hello World!')#output: Hello World!หากการปิดรับเพียงหนึ่งพารามิเตอร์คุณสามารถใช้เพื่ออ้างอิงพารามิเตอร์นี้:
def myclosure = {println it} // ดำเนินการปิดการปิด ('Hello World!')#output: Hello World!การปิดที่ได้รับหลายพารามิเตอร์:
def myclosure = {string str, int num -> println "$ str: $ num"} // ดำเนินการ renovationyclosure ของเรา ('สตริงของฉัน', 21) #output: สตริงของฉัน: 21นอกจากนี้ประเภทของพารามิเตอร์เป็นตัวเลือกและตัวอย่างข้างต้นสามารถย่อได้ดังนี้:
def myclosure = {str, num -> println "$ str: $ num"} // ดำเนินการปิดการเปิดรับของเรา ('สตริงของฉัน', 21) #output: สตริงของฉัน: 21สิ่งที่ยอดเยี่ยมคือตัวแปรจากบริบทปัจจุบันสามารถใช้ในการปิดได้ โดยค่าเริ่มต้นบริบทปัจจุบันคือคลาสที่สร้างการปิด:
def myvar = 'hello world!' def myclosure = {println myvar} myclosure ()#output: สวัสดีโลก!อีกจุดที่ยอดเยี่ยมคือบริบทของการปิดสามารถเปลี่ยนแปลงได้ผ่านการปิด#setDelegate () คุณสมบัตินี้มีประโยชน์มาก:
def myclosure = {println myvar} // ฉันอ้างอิง myVar จาก myclass classmyclass m = myclass () myclosure.setDelegate (m) myclosure () คลาส myclass {def myvar = 'สวัสดีจาก myclass!อย่างที่คุณเห็น MyVar ไม่มีอยู่เมื่อสร้างการปิด ไม่มีปัญหากับเรื่องนี้เพราะเมื่อเราดำเนินการปิด Myvar มีอยู่ในบริบทของการปิด ในตัวอย่างนี้ เนื่องจากฉันเปลี่ยนบริบทเป็น M ก่อนที่จะดำเนินการปิด MyVar จึงมีอยู่
ผ่านการปิดเป็นพารามิเตอร์
ข้อได้เปรียบของการปิดคือสามารถส่งผ่านไปยังวิธีการต่าง ๆ ซึ่งสามารถช่วยให้เราแยกตรรกะการดำเนินการได้ ในตัวอย่างก่อนหน้านี้ฉันได้แสดงวิธีการปิดการปิดไปยังอินสแตนซ์ของคลาส ด้านล่างเราจะดูวิธีการต่าง ๆ ที่ได้รับการปิดเป็นพารามิเตอร์:
1. ได้รับเพียงหนึ่งพารามิเตอร์และพารามิเตอร์คือวิธีการปิด: myMethod (myclosure)
2. หากวิธีการได้รับเพียงหนึ่งพารามิเตอร์สามารถละเว้นวงเล็บได้: mymethod myclosure
3. คุณสามารถใช้การปิดแบบอินไลน์: mymethod {println 'Hello World'}
4. วิธีการรับพารามิเตอร์สองตัว: myMethod (arg1, myclosure)
5. คล้ายกับ 4, การปิดเอกพจน์เป็นแบบอินไลน์: myMethod (arg1, {println 'Hello World'})
6. หากพารามิเตอร์สุดท้ายถูกปิดมันสามารถนำออกจากวงเล็บ: mymethod (arg1) {println 'Hello World'}
ที่นี่ฉันแค่อยากจะเตือนคุณว่าการเขียนของ 3 และ 6 ดูคุ้นเคยหรือไม่?
ตัวอย่าง Gradle
ตอนนี้เราเข้าใจไวยากรณ์พื้นฐานแล้วเราจะใช้มันในสคริปต์ Gradle ได้อย่างไร? มาดูตัวอย่างต่อไปนี้:
buildScript {repositories {jCenter ()} การพึ่งพา {classpath 'com.android.tools.build:gradle:1.2.3'}} AllProjects {repositories {jCenter ()}}} เมื่อคุณรู้ว่าไวยากรณ์ของ Groovy มันง่ายที่จะเข้าใจตัวอย่างข้างต้นหรือไม่?
อันดับแรกคือวิธีการสร้างข้อความซึ่งได้รับการปิด:
def buildscript (ปิดปิด)
ถัดไปคือวิธี AllProjects ซึ่งได้รับพารามิเตอร์การปิด:
def allprojects (ปิดปิด)
คนอื่น ๆ มีความคล้ายคลึงกัน - -
ตอนนี้ดูเหมือนง่ายกว่ามาก แต่ฉันไม่เข้าใจสิ่งหนึ่งนั่นคือวิธีการเหล่านี้กำหนดไว้ที่ไหน คำตอบคือโครงการ
โครงการ
นี่คือกุญแจสำคัญในการทำความเข้าใจสคริปต์ Gradle
บล็อกคำสั่งในระดับสูงสุดของสคริปต์บิลด์จะได้รับการมอบหมายให้กับอินสแตนซ์ของโครงการซึ่งแสดงให้เห็นว่าโครงการเป็นที่ที่ฉันกำลังมองหา
การค้นหาวิธี BuildScript ในหน้าเอกสารของโครงการจะค้นหา buildScript {} สคริปต์บล็อก (บล็อกสคริปต์) ฯลฯ บล็อกสคริปต์คืออะไร? ตามเอกสาร:
บล็อกสคริปต์เป็นวิธีที่ได้รับการปิดเป็นพารามิเตอร์เท่านั้น อ่านเอกสาร BuildScript ต่อไป เอกสารบอกว่าได้รับมอบหมายให้: Scripthandler จาก BuildScript นั่นคือเราผ่านการปิดไปยังวิธี BuildScript และบริบทการดำเนินการขั้นสุดท้ายคือ Scripthandler ในตัวอย่างข้างต้นการปิดของเราส่งผ่านไปยัง BuildScript เรียกใช้ที่เก็บ (การปิด) และวิธีการพึ่งพา (ปิด) เนื่องจากการปิดได้รับความไว้วางใจจาก Scripthandler เราจะมองหาวิธีการพึ่งพาใน Scripthandler
พบโมฆะการพึ่งพา (การปิดการกำหนดค่าการปิด) ตามเอกสารการพึ่งพาจะใช้ในการกำหนดค่าการพึ่งพาสคริปต์ และในที่สุดก็ได้รับความไว้วางใจจากผู้พึ่งพา
ฉันเห็นว่า Gradles ที่ใช้กันอย่างแพร่หลายเป็นอย่างไร การทำความเข้าใจการมอบหมายเป็นสิ่งสำคัญมาก
บล็อกสคริปต์
โดยค่าเริ่มต้นบล็อกสคริปต์จำนวนมากถูกกำหนดไว้ล่วงหน้าในโครงการ แต่ปลั๊กอิน Gradle ช่วยให้เราสามารถกำหนดบล็อกสคริปต์ใหม่ด้วยตนเอง!
ซึ่งหมายความว่าหากคุณโพสต์ {…} ในระดับบนสุดของสคริปต์บิลด์ แต่คุณไม่พบบล็อกสคริปต์หรือวิธีนี้ในเอกสารของ Gradle ในกรณีส่วนใหญ่นี่คือบล็อกสคริปต์บางส่วนที่กำหนดไว้ในปลั๊กอิน
บล็อกสคริปต์ Android
ลองดูที่ไฟล์แอพ Android/build.gradle เริ่มต้น:
ใช้ปลั๊กอิน: 'com.android.application'android {compilesdkversion 22 buildtoolsversion "22.0.1" defaultconfig {applicationid "com.trickyandroid.testapp" minsdkversion 16 targetsdkversion 22 เวอร์ชัน 1 getDefaultProguardFile ('proguard-android.txt'), 'proguard-rules.pro'}}}คำสั่งงาน
ฉันสังเกตเห็นว่าปัญหาส่วนใหญ่ที่ฉันพบเมื่อใช้ Gradle เกี่ยวข้องกับลำดับการดำเนินการของงาน เห็นได้ชัดว่าถ้าบิลด์ของฉันจะทำงานได้ดีขึ้นหากงานของฉันถูกดำเนินการในเวลาที่เหมาะสม ลองมาดูกันว่าวิธีเปลี่ยนลำดับการดำเนินการของงาน
ขึ้นอยู่กับ
ฉันคิดว่าวิธีที่ตรงที่สุดในการอธิบายวิธีที่คุณพึ่งพางานอื่น ๆ เมื่อดำเนินงานของคุณคือการใช้วิธีการพึ่งพา
ตัวอย่างเช่นในสถานการณ์ต่อไปนี้งาน A มีอยู่แล้ว เราต้องการเพิ่มงาน B และการดำเนินการของมันจะต้องเป็นหลังจากการดำเนินการ:
นี่เป็นสถานการณ์ที่ง่ายมากโดยสมมติว่าคำจำกัดความของ A และ B มีดังนี้:
งาน A << {println 'Hello from a'} งาน b << {println 'สวัสดีจาก b'} เพียงแค่เรียก b.dependentson a และไม่เป็นไร
ซึ่งหมายความว่าตราบใดที่ฉันดำเนินการงาน B งาน A จะดำเนินการก่อน
Paveldudka $ Gradle B: Ahello จาก A: Bhello จาก B
นอกจากนี้คุณสามารถประกาศการพึ่งพาในพื้นที่กำหนดค่างาน:
งาน A << {println 'สวัสดีจากงาน'} b {ขึ้นอยู่กับ dolast {println 'สวัสดีจาก b'}}} ถ้าเราต้องการแทรกงานของเราลงในการพึ่งพางานที่มีอยู่
กระบวนการนี้คล้ายกับสิ่งที่เพิ่งเกิดขึ้นในตอนนี้ สมมติว่าการพึ่งพางานต่อไปนี้มีอยู่แล้ว:
งาน a << {println 'สวัสดีจากงาน'} งาน b << {println 'สวัสดีจาก b'} งาน c << {println 'สวัสดีจาก c'} b.dependentson ac.dependentson bเข้าร่วมงานใหม่ของเรา
งาน B1 << {println 'สวัสดีจาก b1'} b1.dependson bc.dependson b1เอาท์พุท:
Paveldudka $ Gradle C: Ahello จาก A: Bhello จาก B: B1HELLO จาก B1: Chello จาก C
โปรดทราบว่าขึ้นอยู่กับการเพิ่มงานลงในคอลเลกชันที่ขึ้นอยู่กับดังนั้นจึงไม่มีปัญหาในการพึ่งพางานหลายงาน
งาน b1 << {println 'สวัสดีจาก b1'} b1.dependson bb1.dependson qเอาท์พุท:
Paveldudka $ Gradle B1: Ahello จาก A: Bhello จาก B: Qhello จาก Q: B1HELLO จาก B1
mustrunafter
ตอนนี้สมมติว่าฉันมีงานอื่นซึ่งขึ้นอยู่กับอีกสองงาน ที่นี่ฉันใช้สถานการณ์จริงที่ฉันมีงานสองงานงานทดสอบหนึ่งหน่วยและงานทดสอบ UI หนึ่งงาน นอกจากนี้ยังมีงานที่ดำเนินการทดสอบทั้งหมดซึ่งขึ้นอยู่กับสองงานก่อนหน้า
หน่วยงาน << {println 'สวัสดีจากหน่วยทดสอบ'} งาน ui << {println 'สวัสดีจากการทดสอบ UI'} การทดสอบงาน << {println 'สวัสดีจากการทดสอบทั้งหมด!'} การทดสอบเอาท์พุท:
การทดสอบ paveldudka $ Gradle: Uihello จากการทดสอบ UI: Unithello จากการทดสอบหน่วย: Testshello จากการทดสอบทั้งหมด!
แม้ว่าการทดสอบ UNTEST และ UI จะดำเนินการก่อนงานทดสอบ แต่ไม่สามารถรับประกันคำสั่งการดำเนินการของหน่วยและ UI ได้ แม้ว่ามันจะถูกดำเนินการตามลำดับของตัวอักษรในขณะนี้ แต่ขึ้นอยู่กับการใช้งาน Gradle และคุณต้องไม่พึ่งพาคำสั่งนี้ในรหัสของคุณ
เนื่องจากเวลาทดสอบ UI นั้นยาวกว่าเวลาทดสอบหน่วยมากฉันต้องการให้การทดสอบหน่วยดำเนินการก่อน ทางออกหนึ่งคือการทำให้งาน UI ขึ้นอยู่กับงานหน่วย
หน่วยงาน << {println 'สวัสดีจากหน่วยทดสอบ'} งาน UI << {println 'สวัสดีจากการทดสอบ UI'} การทดสอบงาน << {println 'สวัสดีจากการทดสอบทั้งหมด!'} การทดสอบเอาท์พุท:
การทดสอบ paveldudka $ Gradle: Unithello จากการทดสอบหน่วย: Uihello จากการทดสอบ UI: Testshello จากการทดสอบทั้งหมด!
ตอนนี้การทดสอบหน่วยจะถูกดำเนินการก่อนการทดสอบ UI
แต่มีปัญหาที่น่าขยะแขยงมากที่นี่ การทดสอบ UI ของฉันไม่ได้พึ่งพาการทดสอบหน่วย ฉันหวังว่าจะสามารถทำการทดสอบ UI แยกกันได้ แต่ที่นี่ทุกครั้งที่ฉันทำการทดสอบ UI ฉันจะทำการทดสอบหน่วยก่อน
จำเป็นต้องใช้ Mustrunafter ที่นี่ Mustrunafter ไม่เพิ่มการพึ่งพา แต่เพียงแค่บอกให้ Gradle ให้ความสำคัญกับการดำเนินการหากมีงานสองงานในเวลาเดียวกัน ตัวอย่างเช่นเราสามารถระบุหน่วย UI.Mustrunafter ได้ที่นี่ ด้วยวิธีนี้หากงาน UI และหน่วยงานมีอยู่ในเวลาเดียวกัน Gradle จะทำการทดสอบหน่วยก่อนและหากมีการดำเนินการเฉพาะ Gradle UI งานหน่วยจะไม่ถูกดำเนินการ
หน่วยงาน << {println 'สวัสดีจากหน่วยทดสอบ'} งาน ui << {println 'สวัสดีจากการทดสอบ UI'} การทดสอบงาน << {println 'สวัสดีจากการทดสอบทั้งหมด!'} การทดสอบเอาท์พุท:
การทดสอบ paveldudka $ Gradle: Unithello จากการทดสอบหน่วย: Uihello จากการทดสอบ UI: Testshello จากการทดสอบทั้งหมด!
ความสัมพันธ์แบบพึ่งพามีดังนี้:
Mustrunafter ปัจจุบันเป็นคุณสมบัติการทดลองใน Gradle 2.4
สรุปโดย
ตอนนี้เรามีสองงานคือหน่วยและ UI โดยสมมติว่างานทั้งสองจะรายงานการทดสอบเอาท์พุทตอนนี้ฉันต้องการรวมรายงานการทดสอบทั้งสองนี้เข้าด้วยกัน:
หน่วยงาน << {println 'สวัสดีจากหน่วยทดสอบ'} งาน ui << {println 'สวัสดีจากการทดสอบ UI'} การทดสอบงาน << {println 'สวัสดีจากการทดสอบทั้งหมด!'} งาน mergereports << {println 'การทดสอบการทดสอบตอนนี้ถ้าฉันต้องการรับรายงานการทดสอบสำหรับ UI และหน่วยงานให้ดำเนินการงาน Mergereports
paveldudka $ gradle mergereports: Unithello จากการทดสอบหน่วย: Uihello จากการทดสอบ UI: Testshello จากการทดสอบทั้งหมด!: รายงานการทดสอบ MergerePortsmerging
งานนี้ใช้งานได้ แต่มันดูโง่มาก Mergereports ไม่รู้สึกดีเป็นพิเศษจากมุมมองของผู้ใช้ ฉันต้องการดำเนินการทดสอบเพื่อรับรายงานการทดสอบโดยไม่ต้องรู้ว่ามีการดำรงอยู่ของ Mergereports แน่นอนว่าฉันสามารถย้ายตรรกะการผสานไปยังงานทดสอบได้ แต่ฉันไม่ต้องการให้งานทดสอบป่องเกินไปดังนั้นฉันจะยังคงวางตรรกะการผสานลงในงาน Mergereports ต่อไป
Finalizeby อยู่ที่นี่เพื่อบันทึกฉาก ตามชื่อที่แนะนำ FinalizeBy เป็นงานที่ต้องดำเนินการหลังจากดำเนินการงาน แก้ไขสคริปต์ของเราดังนี้:
หน่วยงาน << {println 'สวัสดีจากหน่วยทดสอบ'} งาน ui << {println 'สวัสดีจากการทดสอบ UI'} การทดสอบงาน << {println 'สวัสดีจากการทดสอบทั้งหมด!'} งาน mergereports << {println 'การทดสอบการทดสอบ ผู้ค้าขายตอนนี้ดำเนินงานทดสอบและคุณสามารถรับรายงานการทดสอบ:
การทดสอบ paveldudka $ gradle: Unithello จากการทดสอบหน่วย: Uihello จากการทดสอบ UI: Testshello จากการทดสอบทั้งหมด!: รายงานการทดสอบ MergerePortsmerging