+7

Cài đặt Local CI đơn giản cho Android team của bạn

Chào mọi người, hôm rồi mình có hướng dẫn dùng ktlint và detekt để check code style, lint cho android. Hôm nay, tiếp tục seri cải thiện code, mình chia sẻ tiếp về cách chạy các tool trên tự động mỗi khi commit code.

OK, bắt đầu thôi !

Git Hooks

Trước đây, mình vẫn luôn mong muốn tìm một công cụ để giúp loại bỏ những lỗi cơ bản về gitcoding convention trước khi gởi PR, như commit message theo chuẩn công ty, code phải được format chung, unit test phải đc chạy passlocal hay lỗi lint phải được xử lý hết... tìm hoài không thấy nên phải thường nhắc nhở anh em, đôi khi nhắc hoài nó lại dỡ. Bởi lẽ code mọi người đều quản lý trên máy cá nhân, ai làm gì thì sao can thiệp, cứ ngỡ rồi mình sẽ mãi bị ghét vì suốt ngày lãi nhãi, nhắc đi nhắc lại mấy chuyện kiểu "Ừ biết rồi, khổ lắm, nói mãi". Và rồi...

Như vô tình nhặt được bí kíp, mình tình cờ tìm kiếm cách check từ khoá commit message rule gitgit hooks hiện ra như cứu cánh. Với git hooks, mọi vấn đề của mình đã gần như hoàn toàn được giải quyết.

Git hooks là gì?

Về chi tiết các loại git hooks các bạn có thể đọc thêm ở bài viblo này

Giống như các hệ thống quản lý version khác, Git cũng cung cấp cho chúng ta một cách để can thiệp vào một số quá trình đặc biệt của nó bằng những custom script, đó là hook.

Git hooks trở thành vị cứu tinh như thế nào?

Mình chọn ngay 2 client hook phù hợp cho việc kiểm tra commit message rule, và chạy các tool/task tự động code style, lint, unit test trước khi commit.

  1. pre-commit: Hook này được gọi đầu tiên, thực thi trước khi bạn nhập nội dung cho commit message. Hook này được sử dụng để kiểm tra nội dung các tập tin được commit. Bạn có thể viết script để kiểm tra code convention, run test hoặc chạy static analysis trước khi commit. Nếu script trả về kết quả lớn hơn 0, quá trình commit sẽ bị hủy bỏ.

  2. commit-msg: Nó được gọi sau khi chúng ta đã nhập nội dung message cho commit. Thích hợp trong việc chuẩn hóa commit message. Chỉ có một tham số được nhận trong hook này, đó là tên file chứa commit message. Tương tự, nếu script này trả về kết quả lớn hơn 0 thì quá trình commit sẽ bị hủy.

Cài đặt

Tạo một folder team-props có cấu trúc như sau:

Lưu ý: nếu các bạn thay đổi tên hoặc cấu trúc thư mục, các bước bên dưới các bạn phải tự tìm kiếm và sửa đường dẫn đến các file theo đúng thư mục của bạn.

Tạo script checking commit message (commit-msg.sh)

#!/usr/bin/env bash
echo "Checking your commit message"

<<comment
  Bạn có thể comment theo cú pháp này.
  Bạn có thể thay đổi message regex theo mong muốn.
  Lưu ý có sự khác nhau regex trên Mac vs Linux.
comment

commit_regex='^\[\b(add|modify|fix|revert|hotfix)\b\]\s.+$' 
error_msg="Aborting commit. Your commit message does not follow the commit message rule"

if ! grep -iqE "$commit_regex" "$1"; then
    echo "$error_msg" >&2
    exit 1
fi

For Linux:

commit_regex='^\[\b(add|modify|fix|revert|hotfix)\b\]\s.+$' 

For Mac:

commit_regex='^\[\b(?i)(add|modify|fix|revert|hotfix)\b\]\s.+$'

Bạn có thể thay thế regex theo đúng mong muốn, hiện tại mình đang follow theo rule của project.

Ví dụ, những commit sau sẽ là hợp lệ:

[Add] Feature A
[Modify] Feature B
[Fix] Correct layout C
[Revert] Feature B
[Hotfix] Sometime popup is not dismissed in main page production.
...

Tạo script chạy tự động các gradle task và Unit test (pre-commit.sh)

#!/usr/bin/env bash
echo "Running static analysis..."

echo "Start running ktlint"
./gradlew ktlintFormat ktlintCheck --daemon
status0=$?
if [[ "$status0" = 0 ]] ; then
    echo "ktlint found no problems."
else
    echo 1>&2 "ktlint found violations, it could not fix. Please check ktlint report at \"build/reports/ktlint/ktlint.html\" "
    exit 1
fi

echo "Start running detektCheck"
./gradlew detekt --daemon
status1=$?
if [[ "$status1" = 0 ]] ; then
    echo "detekt found no problems."
else
    echo 1>&2 "ktlint found violations. Please check ktlint report at \"build/reports/detekt/detekt-report.html\" "
    exit 1
fi

echo "Start running unit test"
./gradlew testDevelopDebug --daemon
status2=$?
if [[ "$status2" = 0 ]] ; then
    echo "run unit test success."
    git add .
else
    echo 1>&2 "run unit test failure, please re-check"
    exit 1
fi

Trong đoạn code trên mình kết hợp sử dụng lại các gradle task trong bài dùng ktlint và detekt để kiểm tra code convention và code smell .

Lỗi ở điểm nào thì mình dừng ngay tại thời điểm đó luôn. Commit chỉ thành công khi tất cả các bước trên thành công.

Lưu ý: cú pháp run unit test có dạng như bên dưới, bạn có thể tuỳ chỉnh theo cách của project đang hoạt động.

./gradlew test[Flavor][BuildType]

Copy Hooks to git hooks folder

Bây giờ mình cùng tạo 1 gradle task để copy hooks vào đúng thư mục dự án .git/hooks nào.

fun isLinuxOrMacOs(): Boolean {
    return isMac() || isLinux()
}

fun isLinux(): Boolean {
    return System.getProperty("os.name").toLowerCase().contains("linux")
}

fun isMac(): Boolean {
    val osName = System.getProperty("os.name").toLowerCase()
    return osName.contains("mac os")
            || osName.contains("macos")
}

tasks.register("copyGitHooks", Copy::class) {
    description = "Copies the git hooks from team-props/git-hooks to the .git folder."
    val path = if (isLinux()) "$rootDir/team-props/git-hooks/linux"
    else "$rootDir/team-props/git-hooks/mac" // diff paths from Linux & Mac environment
    from(path) {
        include("**/*.sh")
        rename("(.*).sh", "$1")
    }
    into("$rootDir/.git/hooks")
    onlyIf { isLinuxOrMacOs() }
}

tasks.register("installGitHooks", Exec::class) {
    description = "Installs the pre-commit git hooks from team-props/git-hooks."
    group = "git hooks"
    workingDir = rootDir
    setCommandLine("chmod", "-R", "+x", ".git/hooks/")
    dependsOn("copyGitHooks")
    onlyIf { isLinuxOrMacOs() }
    doLast {
        logger.info("Git hook installed successfully!")
    }
}

afterEvaluate {
    tasks["clean"].dependsOn("installGitHooks")
}

Trong đoạn code trên, sẽ có 2 taskcopyGitHooksinstallGitHooks, 2 task sẽ tự động chạy mỗi khi bạn clean code. Do vậy, chỉ khi bạn chạy clean project ít nhất 1 lần thì các (thay đổi) git hooks mới được copy và chạy đúng mong muốn.

Sau khi hoàn thành tạo task này, bạn hãy thêm vào build.gradle level project:

buildscript {

    apply(from = "$rootDir/team-props/git-hooks.gradle.kts")
    
    repositories {
     //...
    }
    dependencies {
     //...
    }
}

Test kết quả

Bây giờ hay clean project 1 lần, sau đó thử edit và tạo 1 commit. Hãy ăn mừng nếu gradle task được chạy thành công ! (beer)(beer)(beer).

Conclusion

Ok, như vậy là mình đã hướng dẫn các bạn tự tạo một local CI nhỏ, giúp check qua code của anh em trong team về code style, lint, unit test trước khi tạo Pull Request. Hy vọng bài viết mang lại lợi ích cho mọi người.

Tham khảo: Source Code

HAPPY CODING !


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí