[VN] Gradle Build File qua lăng kính Groovy

Lập trình viên Android phải thường xuyên làm việc với Gradle build file. Do vậy chắc chúng ta không quá lạ lẫm với đoạn code sau:

apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.silicons.android.uploader"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
}

build.gradle thoạt nhìn vào giống như một file json với các định nghĩa về thuộc tính. Thực tế build.gradle hoàn toàn xây dựng trên ngôn ngữ Groovy và lập trình viên hoàn toàn có thể để vào một số đoạn code Groovy. (ví dụ: tạo ra một task để in ra toàn bộ package dependencies). Hôm nay, chúng ta sẽ nhìn vào file build.gradle bằng lăng kính hiển vị Groovy ^^

Grooooovy

Phần này sẽ đề cập lại một số khái niệm về Groovy có sử dụng trong bài

Groovy Delegate Object

Groovy có một khái niệm rất mạnh tên là delegate. Delegate sẽ tạo ra một môi trường để closure tìm kiếm các methods và các thuộc tính khi chưa được định nghĩa. Demo sau sẽ trình bày khái niệm này:

// create class and instance in Groovy
class GroovyClass {
int a = 3
}
def groovyObject = new GroovyClass()
//a closure uses a property that not defined in current closure scope
def groovyClosure = {
// in Groovy. method call doesn’t need parenthese
println “Current Scope value: “ + a
}
// will compile error because "cannot find property a"
groovyClosure()
// delegate groovyObject as environment for closure
groovyClosure.delegate = groovyObject
groovyClosure() // Print: “Current Scope value: 3”

Groovy Task Type

Groovy có hai loại task: type task and ad-hoc task. Type tasks cung cấp lập trình viên một cách thuận lợi làm một số tác vụ như copy files, nén một thư mục, tạo report, tạo file jar/ear … Type task được lập trình bằng cách sử dụng Gradle DSL. Các bạn có thể tham khảo một số type task được cung cấp mặc định bởi Gradle ở đây.

Ví dụ sau đây sử dụng type task để copy hình ảnh (gồm từ nhiều định dạng ) vào thư mục build.

// declare a type task with following structure
task copyImageFolders(type: Copy) {
from('images') {
include '*.jpg'
into 'jpeg'
}
    from('images') {
include '*.gif'
into 'gif'
}
    into 'build'
}

Let’s staaaaart a tour

Đầu tiên, toàn bộ đoạn build script của Gradle sử dụng delegate tên gọi là project object. “Project object” có một method tên là `task` để khởi tạo task. Method sẽ nhận vào đối số là tên của task và configuration closure (mình sẽ thảo luận ở phần sau)

project.task("myTask1")

Bởi vì toàn bộ đoạn script sử dụng delegate vào project object, chúng ta có thể bỏ đi cách gọi method từ đối tượng project.

task("myTask2")

Tuyệt vời hơn nữa, cú pháp Groovy cho phép chúng ta gọi hàm mà không cần bỏ ngoặc. Một số ngôn ngữ hiện đại sau này như Swift cũng cho phép chúng ta làm tương tự vậy :)

task "myTask3"

Khái niệm tiếp theo tương đối đặc biệt chỉ có trong Gradle. Gradle sử dụng một tính tăng nâng cao trong Groovy(Abstract Syntax Tree Transformation) để thay đổi cú pháp việc khai báo một task khi biên dịch. Do vậy với cú pháp mới, chúng ta thậm chí có thể bỏ đi cả dấu ngoặc “”. Các bạn có thể tham khảo chi tiết hơn tại đây.

task myTask4

Bởi vì task đơn giản cũng chỉ là một đối tượng Groovy nên chúng ta có thể gán các thuộc tính và gọi các method như một đối tượng thông thường.

myTask4.description = "This is what's shown in the task list"
myTask4.group = "This is the heading for this task in the task list"

Một trong những thuộc tính quan trọng của task là cho phép gán những hành động mà task sẽ thực thi.

/*
Instead of setting the `actions` property of our task, we can add a closure to the end of the list using the `doLast` method.
*/
myTask4.doLast { println "Do this last" }
/*
We can also add actions to the start of the list using `doFirst`.
*/
myTask4.doFirst { println "Do this first" }
/*
We can also add actions using 'leftShift' operator
*/
myTask4.leftShift { println "Do this even more last" }
myTask4 << { println "Do this last of all" }

Kết hợp với hai thứ chúng ta vừa nêu ra ở trên, chúng ta có thể khởi tạo task đồng thời gán cho các hành vi cùng lúc. Đây là cách hay được sử dụng trong Gradle.

task myTask5 << {
println "Declare a task and give it an action in one stroke"
}

Thay vì khởi tạo task và gán thuộc tính sau đó, chúng ta có thể sử dụng configuration closure (đã được đề cập phần trên) khi mới khởi tạo. Có 2 lưu ý chính về configuration closure:

  1. Khi mà closure bắt đầu được xử lí, delegate chính là task object. Do vậy, khi chúng ta gán một method hoặc thuộc tính cho `group`, chúng ta thật sự đang gán cho `task.group`.
  2. Trong groovy, thuộc tính của đối tượng task sẽ tự động có setters cùng tên với thuộc tính. Do vậy, đoạn code dưới đây thực chất là lời gọi tới setter mà bỏ đi các dấu ngoặc. Ngoại lệ duy nhất là khi gán một tập hợp (array, set …) cho thuộc tính, chúng ta phải sử dụng toán tử “=”.
task myTask6 {
// Function call. Setter for property "description".
description("Description")
// This is identical to the line above
description "Description"
// assign collection to a property must use =
group = ["dev", "release"]
}

Lập trình viên có thể gán thuộc tính trong configuration block hoặc ngay khi vừa khởi tạo task để tăng tính mạch lạc cho khai báo. Tuy nhiên, một trường hợp đặc biệt, nếu task chúng ta có khởi tạo cùng với một type task, chúng ta buộc phải khởi tạo thuộc tính ngay lập tức, không thể đặt vào trong configuration block được.

task myTask7(description: "Another description") << {
println "Doing something"
}

Kết luận

Qua cuộc hành trình vừa rồi, chúng ta thấy rõ, bản chất của build.gradle vẫn chỉ là một file ngôn ngữ Groovy thuần tuý.

Groovy là một ngôn ngữ mình đánh giá là đẹp, hỗ trợ optionally typed và cả dynamic language, cùng khả năng static-typing static compilation. Groovy cũng rất dễ học với các bạn có background về Java vì Groovy được xây dựng hoàn toàn dựa trên ngôn ngữ này.

Trong các bài viết tiếp theo, mình sẽ thảo luận chuyên sâu hơn về Groovy và Gradle và làm sao để cấu hình Gradle cho nhiều project. Chúc mọi người năm mới làm việc vui vẻ ^^

Link