如何仅在特定文件集发生更改时触发构建

110

我该如何告诉Jenkins/Hudson只针对我Git树中特定项目的更改触发构建?

9个回答

72
如果您正在使用声明性语法的Jenkinsfile来描述您的构建流程,您可以使用changeset条件来仅限于在特定文件更改时执行阶段。这是Jenkins的一个标准功能,不需要任何额外的配置或软件。
stages {
    stage('Nginx') {
        when { changeset "nginx/*"}
        steps {
            sh "make build-nginx"
            sh "make start-nginx"
        }
    }
}

您可以使用anyOfallOf关键字将多个条件组合起来,以实现ORAND的行为:

when {
    anyOf {
        changeset "nginx/**"
        changeset "fluent-bit/**"
    }
}
steps {
    sh "make build-nginx"
    sh "make start-nginx"
}

1
请注意,它并不适用于某些情况。有关更多详细信息,请参阅https://issues.jenkins-ci.org/browse/JENKINS-26354。 - tamerlaha
请注意,当您在多模块构建(例如Maven反应堆)中使用此功能时,它将无法解决和构建这些模块之间的依赖关系。为了实现这一点,您需要一个理解您的构建和依赖关系的构建插件。 - jaw
如果您想生成两个特定分支之间的变更集(例如,如果您正在处理拉取请求,则会有源分支和目标分支),则可以使用Jenkins的Pipeline Syntax Snippet Generator,选择名为“checkout:Check out from version control”的示例步骤,并在“附加行为”下单击“添加”,并添加名为“Calculate changelog against a specific branch”的行为。然后,当您填写字段并单击“生成管道脚本”时,您将为您配置“ChangelogToBranch”选项(它将相应地填充变更集)。 - dlauzon

67

Git插件有一个选项(排除区域),可以使用正则表达式来确定是否根据提交的文件是否匹配被排除的区域正则表达式跳过构建。

不幸的是,目前(1.15)stock Git插件没有“包含区域”功能。然而,有人在GitHub上发布了补丁,适用于Jenkins和Hudson,实现了你想要的功能。

这需要一些工作来构建,但它像广告一样有效,并且对我来说非常有用,因为我的Git树有多个独立的项目。

https://github.com/jenkinsci/git-plugin/pull/49

更新:Git插件(1.16)现在具有“包含”区域功能。


8
包含该功能的正确版本号为1.1.16。(没有1.16版本) - dan carter
我无法让它工作,我有一个包含多个模块(domain、common、api、desktop_app等)的代码库。例如,我想为desktop_app触发构建,我在“包含区域”中放置了production_app/*,我尝试了几种组合,如./desktop_app甚至是绝对路径。但我总是得到忽略提交c6e2b1dca0d1885:没有匹配包含区域白名单的路径。有什么线索吗?更多细节请参见:https://dev59.com/OKbja4cB1Zd3GeqPovo3 - FranAguiar
Bitbucket用户:我可能错了,但这种方法似乎不起作用。我正在发布一个可行的解决方案。 - Bob

39

基本上,你需要两个任务。一个是检查文件是否更改,另一个是执行实际的构建:

任务 #1

此任务应在 Git 存储库中发生更改时触发。然后它会测试您指定的路径(这里是“src”)是否有更改,然后使用 Jenkins CLI 触发第二个任务。

export JENKINS_CLI="java -jar /var/run/jenkins/war/WEB-INF/jenkins-cli.jar"
export JENKINS_URL=http://localhost:8080/
export GIT_REVISION=`git rev-parse HEAD`
export STATUSFILE=$WORKSPACE/status_$BUILD_ID.txt

# Figure out, whether "src" has changed in the last commit
git diff-tree --name-only HEAD | grep src

# Exit with success if it didn't
$? || exit 0

# Trigger second job
$JENKINS_CLI build job2 -p GIT_REVISION=$GIT_REVISION -s

工作 #2

配置此工作以接受名为GIT_REVISION的参数,以确保您正在构建第一个工作选择要构建的确切修订版本。

Parameterized build string parameter Parameterized build Git checkout


7
如果最后一次构建之后发生了两个或更多个提交,会怎样?我认为你可能会错过源代码中的更改,因为你只检查了最新的提交(HEAD commit)。 - Adam Monsen
@AdamMonsen 没错。但是你可以轻松地将上述脚本适应于你想要测试的任何情况/条件,例如不与 HEAD 进行比较,而是与上次脚本运行时的 HEAD 进行比较。 - peritus
$? || exit 0 中似乎缺少了一些东西... test $? -eq 0 || exit 0 可能是正确的吗? - antak
在我的公司中,由于安全原因,jenkins-cli.jar 被禁用了。那么,在 jenkins 的 源代码管理 部分,如何指定 仓库 URL 上的 GIT-REVISION?对于 SVN,我们可以在 URL 后缀中添加类似于 @${repo_rev} 的内容。 - Heinz
看起来像是我可以放进管道的.when{}块中,以使其具有条件性,对吗? - undefined

11

虽然这并不影响单个作业,但是如果最新的提交没有包含任何更改,您可以使用此脚本忽略某些步骤:

/*
 * Check a folder if changed in the latest commit.
 * Returns true if changed, or false if no changes.
 */
def checkFolderForDiffs(path) {
    try {
        // git diff will return 1 for changes (failure) which is caught in catch, or
        // 0 meaning no changes 
        sh "git diff --quiet --exit-code HEAD~1..HEAD ${path}"
        return false
    } catch (err) {
        return true
    }
}

if ( checkFolderForDiffs('api/') ) {
    //API folder changed, run steps here
}

1
@Karl,如果这段代码对你有用,请随意进行修复。当我应用这段代码时,唯一的问题就是在构建失败时,如果最新提交没有更改api/文件夹,它不会重试此提交。如果您能解决这个问题,我很乐意接受您的建议! - Blue

5
针对使用源代码管理平台(如Bitbucket Repository)的用户,以及其他使用Webhook负载未指示文件更改的人员。
似乎 Git 插件“包含区域”无论我怎么做都会失败,并且始终触发构建。我的设置是 Jenkins 2.268,在 Docker 容器中运行,并且找到实现根据文件更改构建作业的正确方法非常困难,以下是一种方法:
所需 Jenkins 插件:
- Groovy - Bitbucket(或者如果您使用的是另一个 SCM 主机:可以在该主机的 Webhook 上触发构建的插件)
创建一个名为“Switch”的新“自由风格”作业:
- 源代码管理:指示您的 SCM 信息(确保要构建的分支是正确的)。 - 构建触发器 > 当有变更被推送到 Bitbucket 时构建:选中。 - 构建步骤 > 执行系统 Groovy 脚本(不仅仅是执行 Groovy 脚本!),并保留“不使用 Groovy 沙箱”的选项未选中。
脚本内容:
import jenkins.*;
import jenkins.model.*;

// CONFIGURATION
// Links between changed file patterns and job names to build
def jobsByPattern = [
  "/my-project/": "my-project-job",
  "my-super-project/":"super-job",
]

// Listing changes files since last build
def changeLogSets = build.changeSets
def changedFiles = []
for (int i = 0; i < changeLogSets.size(); i++) {
   def entries = changeLogSets[i].items
   for (int j = 0; j < entries.length; j++) {
    def entry = entries[j]
    def files = new ArrayList(entry.affectedFiles)
    for (int k = 0; k < files.size(); k++) {
       def file = files[k]
       changedFiles.add(file.path)
    }
  }
}

// Listing ad hoc jobs to build
jobsToBuild = [:] // declare an empty map
for(entry in jobsByPattern ) {
  def pattern = entry.key
  println "Check pattern: $pattern"
  for (int i = 0; i < changedFiles.size(); i++) {
    def file = changedFiles[i]
    println "Check file: $file"
    if( file.contains( pattern ) ) {
      def jobName = entry.value
      jobsToBuild[ jobName ] = true
      break
    }
  }
}

// Building appropriate jobs
jobsToBuild.each{
  def jobName = it.key
  println "$jobName must be built!"
  def job = Jenkins.instance.getJob(jobName)
  def cause = new hudson.model.Cause.UpstreamCause(build)
  def causeAction = new hudson.model.CauseAction(cause)
  Jenkins.instance.queue.schedule(job, 0, causeAction)
}

我相信这种方法可以处理自上次构建以来的多个提交,因此它似乎能够满足需要。欢迎提出任何增强建议。

4
我编写了这个脚本来跳过或执行测试,如果有更改的话:
#!/bin/bash

set -e -o pipefail -u

paths=()
while [ "$1" != "--" ]; do
    paths+=( "$1" ); shift
done
shift

if git diff --quiet --exit-code "${BASE_BRANCH:-origin/master}"..HEAD ${paths[@]}; then
    echo "No changes in ${paths[@]}, skipping $@..." 1>&2
    exit 0
fi
echo "Changes found in ${paths[@]}, running $@..." 1>&2

exec "$@"

所以你可以像这样做:
./scripts/git-run-if-changed.sh cmd vendor go.mod go.sum fixtures/ tools/ -- go test

2
如果选择文件的逻辑不是简单的,我会在每次更改时触发脚本执行,然后编写一个脚本来检查是否确实需要进行构建,如果需要,则触发构建。

2
您可以使用通用 Webhook 触发器插件来实现此目的。
使用像changed_files这样的变量和表达式$.commits [*]. ['modified','added','removed'] [*]
您可以使用过滤文本$changed_files和过滤正则表达式"folder/subfolder/ [^"]+?",如果folder/subfolder是应触发构建的文件夹。

我正在尝试做这件事,但有点迷失。如何将更改文件的路径发送到Jenkins?你能再解释一下吗?应该把变量changed_files放在哪里? - Souad
你需要在使用的 Git 服务中配置一个 Webhook。如果是 GitHub,可以参考这里的示例:https://github.com/jenkinsci/generic-webhook-trigger-plugin/blob/master/src/test/resources/org/jenkinsci/plugins/gwt/bdd/github/github-push-get-changed-files.feature - Tomas Bjerre
事实上,由于我正在使用Bitbucket,我意识到在bibucket的推送事件负载中,Changed_files条目不可用(参考:https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html#EventPayloads-Push),因此我不确定该如何处理。我想我会依赖提交消息。谢谢。 - Souad
@Souad:这里出现了完全相同的问题。我找到了一种方法,已在此发布。它有用吗? - Bob

1

我在另一篇文章中回答了这个问题:

如何在Jenkins/Hudson中获取自上次构建以来更改的文件列表

#!/bin/bash

set -e

job_name="whatever"
JOB_URL="http://myserver:8080/job/${job_name}/"
FILTER_PATH="path/to/folder/to/monitor"

python_func="import json, sys
obj = json.loads(sys.stdin.read())
ch_list = obj['changeSet']['items']
_list = [ j['affectedPaths'] for j in ch_list ]
for outer in _list:
  for inner in outer:
    print inner
"

_affected_files=`curl --silent ${JOB_URL}${BUILD_NUMBER}'/api/json' | python -c "$python_func"`

if [ -z "`echo \"$_affected_files\" | grep \"${FILTER_PATH}\"`" ]; then
  echo "[INFO] no changes detected in ${FILTER_PATH}"
  exit 0
else
  echo "[INFO] changed files detected: "
  for a_file in `echo "$_affected_files" | grep "${FILTER_PATH}"`; do
    echo "    $a_file"
  done;
fi;

您可以直接将检查添加到作业的执行 shell 顶部,如果未检测到更改,则它将退出 0... 因此,您始终可以轮询顶层以触发构建。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接