給 Java gradle 工程添加 git hooks
接《后端工程圣殿形象的崩塌以及重建》,今天記錄一下使用土辦法給 Java gradle 工程添加 git hooks 的步驟,并且創(chuàng)建了一個(gè)樣例項(xiàng)目和提交記錄放在了 GitHub:https://github.com/Jeff-Tian/securing-web-with-wechat-mp/commit/2a844e890cb0d91eaf5b5684f9d750cf586613a2。歡迎?? 圍觀和批評(píng),畢竟這是一個(gè)很土的辦法。
背景簡(jiǎn)介
Git 鉤子可以在某些事件發(fā)生時(shí),執(zhí)行指定的任務(wù)。比如我希望在自己的工程里,當(dāng)進(jìn)行 git commit 和 push 前,跑一邊測(cè)試,確保新的代碼改動(dòng)沒(méi)有帶來(lái)問(wèn)題,那么就可以分別添加 pre-commit 和 pre-push 兩個(gè)鉤子事件任務(wù)。
默認(rèn)的 Git 鉤子目錄是 .git/hooks。這個(gè)目錄就是 Git 會(huì)在項(xiàng)目的事件發(fā)生時(shí)去檢查可能需要執(zhí)行的腳本的地方。
思路
還是因?yàn)閷?duì) Java 的世界不熟悉,因此走了土辦法途徑。很容易想到,直接寫(xiě)兩個(gè)腳本,分別對(duì)應(yīng) pre-commit 和 pre-push,放在 .git/hooks 目錄下。但這樣做的缺點(diǎn)是,當(dāng)你換一臺(tái)機(jī)器時(shí),這個(gè)信息就丟失了,因?yàn)檫@個(gè)改動(dòng)沒(méi)有納入到源代碼管理中。
所以想到的改進(jìn)方案時(shí)將腳本寫(xiě)在源代碼里,然后向 build.gradle 文件中添加一個(gè)安裝鉤子的任務(wù),這樣一來(lái),所有拉去改項(xiàng)目的機(jī)器,都可以一鍵安裝鉤子。
實(shí)現(xiàn)
創(chuàng)建腳本
針對(duì)背景簡(jiǎn)介里的需求,一共需要?jiǎng)?chuàng)建兩種鉤子,分別是 pre-commit 和 pre-push。為了支持 macOS 和 windows 開(kāi)發(fā)機(jī),對(duì)每種鉤子分別創(chuàng)建兩個(gè)文件。以 pre-push 為例,分別創(chuàng)建 pre-push-macos 和 pre-push-windows 兩個(gè)文件,如下:
pre-push-macos:
#!/bin/bash
echo "運(yùn)行測(cè)試..."
./gradlew test --info
status=$?
# 如果測(cè)試失敗會(huì)返回碼置為 1
[ $status -ne 0 ] && exit 1
exit 0
pre-push-windows:
#!C:/Program\ Files/Git/usr/bin/sh.exe
echo "運(yùn)行測(cè)試..."
./gradlew test --info
status=$?
# 如果測(cè)試失敗會(huì)返回碼置為 1
[ $status -ne 0 ] && exit 1
exit 0
兩個(gè)文件幾乎一致,只是第一行 She bang line 有區(qū)別,因?yàn)檫@兩種系統(tǒng)對(duì)應(yīng)的可以執(zhí)行的 sh 文件所在目錄有區(qū)別。
向 build.gradle 文件添加安裝鉤子的任務(wù)
import org.apache.tools.ant.taskdefs.condition.Os
task installGitHooks(type: Copy) {
def suffix = "macos"
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
suffix = "windows"
}
from new File(rootProject.rootDir, "scripts/pre-commit-$suffix")
into { new File(rootProject.rootDir, '.git/hooks') }
rename("pre-commit-$suffix", 'pre-commit')
from new File(rootProject.rootDir, "scripts/pre-push-$suffix")
into { new File(rootProject.rootDir, '.git/hooks') }
rename("pre-push-$suffix", 'pre-push')
fileMode 0775
}
tasks.getByPath(':build').dependsOn installGitHooks
這個(gè)任務(wù)會(huì)判斷系統(tǒng)的類型,并拷貝相應(yīng)的腳本文件到 .git/hooks 目錄下。這樣當(dāng)執(zhí)行
./gradlew installGitHooks
命令時(shí)就會(huì)安裝 Git 鉤子。注意這里還指定 build 任務(wù)依賴 installGitHooks 任務(wù),于是即使執(zhí)行 ./gradlew build 也會(huì)觸發(fā)自動(dòng)安裝 Git 鉤子。
CI/CD
當(dāng)然,在提交前運(yùn)行 Git 鉤子確保測(cè)試通過(guò)是很好的實(shí)踐,但是不排除仍然可以繞過(guò),以至于沒(méi)有測(cè)試就推送了代碼到倉(cāng)庫(kù)。為了避免將不工作的代碼部署到線上(這里開(kāi)發(fā)環(huán)境也屬于線上,線上不單指生產(chǎn)環(huán)境),那么有必要在 CI/CD 環(huán)節(jié)也加上運(yùn)行測(cè)試步驟。如果使用 GitHub Actions,你可以寫(xiě)一個(gè)如下的 .github/workflows/gradle.yml 文件,注意,下面的示例還在 Github Action 里集成了 Sonar 掃描步驟:
name: Java CI with Gradle
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
sonar-build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle packages
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew build sonarqube --info
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Test with Gradle
run: ./gradlew test
這樣你就可以在提交歷史里查看每次提交的測(cè)試結(jié)果和代碼掃描結(jié)果了:

