Jenkins SCM實現Git倉庫定期編譯
本文解決的問題:
- 利用Jenkins SCM定期觸發GitSCM構建。
- 如何配置Jenkins SCM機制。
- 如何寫一個jenkinsfile,學習基本的Groovy DSL語法。
- GitSCM插件,通過GIT_BRANCH變量得到分支名。
- 將本地分支切換到觸發構建的分支,將所有子模塊切換到同名的分支并git pull最新代碼。
Jenkins是一款自動化任務的軟件。軟件開發經常需要編譯版本發布。于是我們可以利用Jenkins定期獲得代碼進行編譯。
一、Jenkins運行原理
- Jenkins官方文檔: https://www.jenkins.io/doc/book/pipeline/getting-started/
- Jenkins文檔: https://rtyler.github.io/jenkins.io/doc/book/pipeline/getting-started/#defining-a-pipeline-in-scm
參考:jenkins原理圖

日常工作場景為開發者將代碼提交到代碼服務器如:Github。如果要實現自動化編譯。基本步驟如下:
- 首先安裝Jenkins后,Jenkins Agent有完整的編譯環境,與開發者代碼編譯環境完全一樣。
- 在Jenkins Controller上創建流水線(pipline)任務。例如:SCM任務可以定期去代碼服務器上拉取代碼。
二、Jenkins SCM
本章內容解決每周自動觸發編譯的問題,在滿足以下條件時執行編譯:
- 代碼倉庫在本周有新的代碼提交。
- 代碼倉庫如果有多個不同分支有新提交,則采用不同編譯時段錯開。
創建Jenkins任務步驟:
- Jenkins上創建任務(Job)名為:
build_pipline。 - 進入任務設置界面:Dashboard >>
build_pipline。 - 在Jenkins配置界面 “構建觸發器” 中選擇任務觸發條件:定時構建,輪詢SCM,觸發遠程構建等。這些條件之間是或的關系,也就是任何一個條件滿足都會運行任務(Job)。
- 在Jenkins配置界面 “流水線” 中設置任務執行的具體內容。
Jenkins的任務執行規則為首先根據構建觸發器里的條件滿足后觸發Jenkins任務啟動,任務查找流水線里定義的執行內容。流水線由Groovy腳本編寫,里面定義了不同的stage階段。Jenkins是由第一個stage執行到最后一個stage,執行完畢后返回執行結果。
Jenkins配置界面如下:

2.1 配置構建觸發器:輪詢SCM
勾選輪詢SCM后需要配置“日程表”設置周期。點圖標“?”號可以獲取幫助提示。
輸入格式為:
MINUTE HOUR DOM MONTH DOW
說明:
MINUTE: Minutes within the hour (0–59)
HOUR: The hour of the day (0–23)
DOM: The day of the month (1–31)
MONTH: The month (1–12)
DOW: The day of the week (0–7) where 0 and 7 are Sunday.
2.2 配置流水線:GitSCM
環境要求:Jenkins安裝GitSCM插件。
參考:
[!NOTE] SCM配置
https://dev.to/logesh-sakthivel/jenkins-pipeline-script-from-scm-2k3k
PrerequisiteJenkinsfile must be located in the SCM(Github,Bitbucket,…)
配置步驟:
- 定義選擇:Pipline Script from SCM。
- SCM中,因為我用的是Git就選Git。
- Repositories:填上倉庫URL,也就是git clone的地址。如:
ssh://myuser@mygitserver/git/myrepo。注意網絡一定是能通這個倉庫地址的。 - Credentials:設置倉庫的賬號密碼。建議設置好免密登錄。
- 指定分支:選擇你需要定期編譯的分支名。如:
*/master。 - 腳本路徑:jenkinsfile目錄。所以代碼倉庫
myrepo需要準備好jeninsfile。jenkinsfile里面的內容是定義流水線的執行階段。下一章具體講到jenkinsfile的語法規則。
配置界面如下:

2.2.1 Repositories:倉庫地址
填寫的倉庫地址一定是Jenkins服務器能連接的。如果倉庫是一個私有Gitlab的倉庫。可以在Gitlab服務器上配置好jenkins的ssh key認證后就可以免密登錄。雖然可以在Credentials設置好賬號密碼但對于測試不是很方便。
在調試階段需要設置一個方便測試的倉庫地址,一般不是Gitlab這樣的團隊公共地址。如果倉庫地址是自己的一臺主機的git倉庫,可以通過下面方法設置好免密。使用jenkins運行任務的賬號,登錄jenkins服務器:
// 設置免密登錄git倉庫服務器
ssh-copy-id myuser@172.2.0.3
因為git倉庫地址也是ssh的,所以只要jenkins服務器能ssh到git倉庫的服務器就可以。
2.2.2 jenkinsfile目錄
我的倉庫名為myrepo,則jenkinsfile路徑為:
myrepo/mydir/jenkinsfile
2.3 定時構建和輪詢(SCM)的區別
- 定時構建:即使沒有代碼變更也計劃執行。
- 輪詢SCM:定期檢查SCM(如:Git倉庫),檢測到有新提交時才觸發構建。
三、jenkinsfile
解決的問題:
- 定義clone了倉庫后如何編譯。
參考:
[!NOTE]
- web界面創建
- Pipeline script from SCM(How to create Jenkins File)
Create a text file and name it Jenkinsfile.txt. Place your pipeline code in this text file. Add this text file to the root folder of your project in the code repository.We can add post-build actions in the Jenkins file. These actions will be executed every time after work is finished.
jenkinsfile由Groovy DSL語法定義了流水線的工作流(work flow)。
3.1 Groovy語法
參考:
- [Jenkins構建腳本 - 簡書 (jianshu.com)](https://www.jianshu.com/p/1a4f35f9678d
- 【Jenkinsfile語法示例:parameters參數化構建】
3.1.1 注釋
舉例:
/* stage('mystage') {
steps {
script {
// 打印。 Groovy單行注釋
echo "branch: ${env.TARGET_BRANCH}"
}
sh '''
# 更新子模塊的遠程分支信息 <<< shell里注釋采用shell注釋語法
git submodule sync --recursive
"""
}
} */ <<< Groovy多行注釋
3.2 設置子模塊默認分支
解決的問題:
- 子模塊如何有默認的本地分支。
- 如何關聯到倉庫
.git/modules/config目錄下配置的注分支。
默認情況下git clone下來的代碼子模塊是不會自動拉取的。
build_pipline$ git submodule
-1df5654c75 mysubmodule_1
并且本地分支未創建,子模塊也沒有submodule upate --init。這種情況我們要利用Jenkins的GitSCM配置extensions。
extensions: [
// 強制創建myrepo倉庫的本地分支
[$class: 'LocalBranch', localBranch: env.TARGET_BRANCH],
// 實現子模塊遞歸檢出記住的commit id
//[$class: 'SubmoduleOption', recursiveSubmodules: true],
],
在git倉庫中會設置默認的分支
$ cat myrepo/.git/modules/mysubmodule_1/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../mysubmodule_1
[remote "origin"]
url = ssh://mygitserver/mysubmodule_1
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
這樣子模塊默認有本地master分支且追蹤了遠端遠端master分支。
mysubmodule_1$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
/workspace/build_pipline/mysubmodule_1$ git pull
Already up to date.
3.3 子模塊切分支
遍歷所有子模塊并切到與GIT_BRANCH同名的本地分支。采用的是調用shell腳本利用git命令實現。
// 更新子模塊并追蹤遠端分支
script {
sh '''
git checkout -B "${TARGET_BRANCH}" --track "${GIT_BRANCH}"
git submodule update --init --recursive
git submodule foreach --recursive "
if ! git rev-parse --verify "${TARGET_BRANCH}" >/dev/null 2>&1; then
# 分支不存在時,創建并跟蹤遠程分支
git checkout -b "${TARGET_BRANCH}" --track "${GIT_BRANCH}"
else
# 分支存在時,切換到該分支并拉取更新
git checkout "${TARGET_BRANCH}" && git pull
fi
"
'''
}
3.4 設置環境變量將代碼clone到指定目錄
在 Jenkins 的 Git SCM 配置中,默認會將代碼克隆到工作空間(Workspace)的根目錄。若需將代碼克隆到?指定子目錄,可通過 extensions 中的 CheckoutToSubdirectory 擴展實現。
extensions: [
// 關鍵配置:指定克隆目錄為工作空間下的 `myrepo` 子目錄
[$class: 'CheckoutToSubdirectory', parentDir: 'myrepo'] //目標目錄路徑(相對工作空間)
]
高級場景:動態目錄生成。若目錄名需基于環境變量動態生成:
script {
def envDir = "build-${env.BUILD_ID}" // 動態目錄名(如 build-123)
checkout([
$class: 'GitSCM',
extensions: [
[$class: 'CleanBeforeCheckout'], // 清理目標目錄
[$class: 'CheckoutToSubdirectory', parentDir: envDir]
]
])
}
3.5 jenkinsfile實現示例
實現了SCM編譯,拉取git倉庫并切出本地分支。jenkinsfile示例如下:
pipeline {
agent any
environment {
APP_NAME = "MYAPP"
}
stages {
stage('GitSCMEnv') {
steps {
script {
env.TARGET_BRANCH = env.GIT_BRANCH.split('/').last()
echo "branch: ${env.TARGET_BRANCH}"
checkout([
// 作用是聲明使用Git作為源代碼管理工具,并配置與之相關的核心參數
$class: 'GitSCM',
// 指定待檢出的分支
branches: [[name: env.GIT_BRANCH]],
// 配置遠程倉庫地址和認證信息
userRemoteConfigs: scm.userRemoteConfigs,
extensions: [
// 強制創建myrepo本地分支,不包括子模塊
//[$class: 'LocalBranch', localBranch: env.TARGET_BRANCH],
[$class: 'LocalBranch',
localBranch: env.TARGET_BRANCH,// 動態指定本地分支名
trackingBranch: env.GIT_BRANCH // 綁定遠程分支
],
// 子模塊遞歸檢出的是記住的commit id,子模塊默認的本地分支
[$class: 'SubmoduleOption',
recursiveSubmodules: true, // 遞歸更新嵌套子模塊
parentCredentials: true, // 統一使用父倉庫憑證
shallow: false, // 禁用淺克隆確保可pull
trackingSubmodules: true // 跟蹤子模塊遠程分支
],
[$class: 'CleanBeforeCheckout'], // 清理之前的同名目錄
]
])
}
sh """
echo "Application Name: \${APP_NAME}"
pwd
ls -la
env | sort
"""
}
}
// 更新子模塊并追蹤遠端分支
stage('Submodules'){
steps {
script {
sh '''
git checkout -B "${TARGET_BRANCH}" --track "origin/${TARGET_BRANCH}"
git submodule update --init --recursive
git submodule foreach --recursive "
if ! git rev-parse --verify "${TARGET_BRANCH}" >/dev/null 2>&1; then
# 分支不存在時,創建并跟蹤遠程分支
git checkout -b "${TARGET_BRANCH}" --track "origin/${TARGET_BRANCH}"
else
# 分支存在時,切換到該分支并拉取更新
git checkout "${TARGET_BRANCH}" && git pull
fi
"
'''
}
}
}
/* stage('build') {
steps {
sh './build.sh' // 執行編譯腳本
}
} */
}
}
四、調試方法
4.1 直接登錄到Jenkins服務器上查看
可以到Jenkins的工作目錄查看實際clone的代碼,切的分支。
// 登錄Jenkins用戶
su jenkins
/var/lib/jenkins/workspace/build_pipline/
4.2 查看日志:Console Output
Dashboard >> build_pipline >> {job} >> Console Output
查看jenkins各個stage的日志打印
4.2.1 打印出環境變量日志
+ env
+ sort
APP_NAME=SIMPLE_APP
BUILD_DISPLAY_NAME=#17
BUILD_ID=17
BUILD_NUMBER=17
BUILD_TAG=jenkins-build_pipline-17
BUILD_URL=[http://myjenkins:8080/job/build_pipline/17/](http://myjenkins:8080/job/build_pipline/17/)
CI=true
EXECUTOR_NUMBER=1
GIT_BRANCH=origin/master
GIT_COMMIT=131d6ae6e
GIT_PREVIOUS_COMMIT=131d6ae6e
GIT_PREVIOUS_SUCCESSFUL_COMMIT=131d6ae6e
GIT_URL=ssh://jenkins@mygitserver/mybuild
HOME=/var/lib/jenkins
HUDSON_COOKIE=463b-f94f-4f72-aea4-0400
HUDSON_HOME=/var/lib/jenkins
HUDSON_SERVER_COOKIE=1de5db4ef9069b3e
HUDSON_URL=[http://myjenkins:8080/](http://myjenkins:8080/)
INVOCATION_ID=6420
JENKINS_HOME=/var/lib/jenkins
JENKINS_NODE_COOKIE=4f2d-d9ff-4921-866d-ac9f
JENKINS_SERVER_COOKIE=durable-5059
JENKINS_URL=[http://myjenkins:8080/](http://myjenkins:8080/)
JOB_BASE_NAME=build_pipline
JOB_DISPLAY_URL=[http://myjenkins:8080/job/build_pipline/display/redirect](http://myjenkins:8080/job/build_pipline/display/redirect)
JOB_NAME=build_pipline
JOB_URL=[http://myjenkins:8080/job/build_pipline/](http://myjenkins:8080/job/build_pipline/)
JOURNAL_STREAM=8:74272520
LANG=en_US.UTF-8
LC_ADDRESS=zh_CN.UTF-8
LC_IDENTIFICATION=zh_CN.UTF-8
LC_MEASUREMENT=zh_CN.UTF-8
LC_MONETARY=zh_CN.UTF-8
LC_NAME=zh_CN.UTF-8
LC_NUMERIC=zh_CN.UTF-8
LC_PAPER=zh_CN.UTF-8
LC_TELEPHONE=zh_CN.UTF-8
LC_TIME=zh_CN.UTF-8
LOGNAME=jenkins
NODE_LABELS=built-in
NODE_NAME=built-in
NOTIFY_SOCKET=/run/systemd/notify
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
PWD=/var/lib/jenkins/workspace/build_pipline
RUN_ARTIFACTS_DISPLAY_URL=[http://myjenkins:8080/job/build_pipline/17/display/redirect?page=artifacts](http://myjenkins:8080/job/build_pipline/17/display/redirect?page=artifacts)
RUN_CHANGES_DISPLAY_URL=[http://myjenkins:8080/job/build_pipline/17/display/redirect?page=changes](http://myjenkins:8080/job/build_pipline/17/display/redirect?page=changes)
RUN_DISPLAY_URL=[http://myjenkins:8080/job/build_pipline/17/display/redirect](http://myjenkins:8080/job/build_pipline/17/display/redirect)
RUN_TESTS_DISPLAY_URL=[http://myjenkins:8080/job/build_pipline/17/display/redirect?page=tests](http://myjenkins:8080/job/build_pipline/17/display/redirect?page=tests)
SHELL=/bin/bash
STAGE_NAME=Stage 2
USER=jenkins
WORKSPACE_TMP=/var/lib/jenkins/workspace/build_pipline@tmp
WORKSPACE=/var/lib/jenkins/workspace/build_pipline
4.3 Git 輪詢日志
Dashboard >> build_pipline >> Git 輪詢日志
[poll] Latest remote head revision on refs/heads/master is: 131d6ae6e - already built by 16
Done. Took 0.29 sec
No changes <<< 提示git倉庫沒有新提交所以沒有觸發編譯。
五、遇到的問題
5.1 如何設置免密git clone : Permission denied (publickey,password).
5.2 報錯:Bad substitution
現象:
[Shell Script -- git submodule update --init --recursive git submodule foreach --recursive " if ! git rev-parse --verify "${env.targetBranch}" >/dev/null 2>&1; then git checkout -b ${env.targetBranch} --track "origin/${env.targetBranch}" else echo "${targetBranch}" fi "]
(http://myjenkins:8080/job/build_pipline/48/execution/node/45/log) (self time 533ms)
+ git submodule update --init --recursive
/var/lib/jenkins/workspace/build_pipline@tmp/durable-5a39671e/script.sh: 3: Bad substitution
解決:
語法錯誤。變量不需要加env,將${env.targetBranch}替換為${targetBranch}
5.3 submoduleCfg配置報錯:git.SubmoduleConfig.branches
現象:
// 語句
checkout([
$class: 'GitSCM',
submoduleCfg: [
[path: 'modules/mysubmodule_1', branches: env.TARGET_BRANCH],
]
])
報錯
java.lang.ClassCastException: hudson.plugins.git.SubmoduleConfig.branches expects java.util.Collection<java.lang.String> but received class java.lang.String
解決:變量加上中括號。
// 子模塊分支綁定,(不過測試由于Jenkins版本不支持并為生效)
submoduleCfg: [
[path: 'modules/mysubmodule_1', branches: [env.TARGET_BRANCH]]
]

浙公網安備 33010602011771號