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

Posted

技术标签:

【中文标题】仅当特定文件集发生更改时如何触发构建【英文标题】:How to trigger a build only if changes happen on particular set of files 【发布时间】:2011-07-11 17:52:12 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

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

构建起来需要一点工作,但它可以像宣传的那样工作,并且非常有用,因为我的一个 Git 树有多个独立的项目。

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

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

【讨论】:

1.1.16 是包含功能的正确版本号。 (没有1.16) 我无法让它工作,我有一个包含多个模块(域、通用、api、desktop_app,...)的存储库,例如,我想为 desktop_app 触发构建,我把“包含区域”production_app/*,我尝试了几种组合,例如 ./desktop_app 甚至是绝对路径。我总是得到Ignored commit c6e2b1dca0d1885: No paths matched included region whitelist。有什么线索吗?更多细节在这里:***.com/questions/47439042/… Bitbucket 用户:我可能错了,但这种方法似乎不起作用。我正在发布一个可行的解决方案。【参考方案2】:

如果您使用 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"

【讨论】:

请记住,它在某些情况下不起作用。详情请参阅issues.jenkins-ci.org/browse/JENKINS-26354。 另外请记住,当您在多模块构建(例如 Maven 反应器)中使用此功能时,它不会解析和构建这些模块之间的依赖关系。为了实现这一点,您宁愿需要一个能够理解您的构建和依赖关系的构建插件。 如果你想在两个特定分支之间生成变更集(例如,如果你正在处理一个拉请求,你会有源分支和目标分支),你可以使用 Jenkins 的流水线语法片段生成器,选择名为“签出:从版本控制签出”的示例步骤,然后在“其他行为”下,单击“添加”并添加名为“针对特定分支计算更改日志”的行为。然后,当您填写字段并单击“生成管道脚本”时,将为您配置“ChangelogToBranch”选项(它将相应地填充变更集)。【参考方案3】:

基本上,您需要两份工作。一个检查文件是否更改,一个进行实际构建:

工作 #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 参数,以确保您构建的正是第一个作业选择构建的修订版。

【讨论】:

如果自上次构建以来发生了两次或多次提交怎么办?我认为您可能会错过 src 中的更改,因为您只是在检查 HEAD 提交。 @AdamMonsen 对。但是您可以轻松地将上述脚本调整为您想要测试的任何情况/条件。例如,不是针对 HEAD,而是针对上次脚本运行时的 HEAD。 $? || exit 0 处缺少某些东西...test $? -eq 0 || exit 0 可能吗? jenkins-cli.jar 在我的公司出于安全原因被禁用,我如何在 源代码管理存储库 URL 上指定 GIT-REVISION > 詹金斯的部分?对于 SVN,我们可以在 url 后面加上 @$repo_rev.【参考方案4】:

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

/*
 * 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

【讨论】:

@Karl 如果代码适合您,请随时修复代码。这是我在应用此代码时遇到的一个问题(在构建失败时,它不会重试此提交,如果绝对最新的提交也没有更改 api/ 文件夹。)如果你能解决这个问题,我会喜欢建议的更改!【参考方案5】:

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

【讨论】:

【参考方案6】:

您可以为此使用Generic Webhook Trigger Plugin。

使用像changed_files 这样的变量和表达式$.commits[*].['modified','added','removed'][*]

如果folder/subfolder 是应该触发构建的文件夹,您可以使用$changed_files 之类的过滤器文本和"folder/subfolder/[^"]+?" 之类的过滤器正则表达式。

【讨论】:

我正在尝试这样做,但我有点迷路了。如何将更改文件的路径发送给詹金斯?你能解释一下吗?将变量 changed_files 放在哪里? 你需要在你使用的 Git 服务中配置一个 webhook。如果是 GitHub,这里有一个例子:github.com/jenkinsci/generic-webhook-trigger-plugin/blob/master/… 事实上,当我使用 Bitbucket 时,我意识到 Changed_files 条目在 bibucket 中推送事件的有效负载中不可用(参考:confluence.atlassian.com/bitbucket/…)所以我不确定我该怎么做这。我将依赖我认为的提交消息。谢谢 @Souad :这里的问题完全相同。我找到了一种方法,我在这里发布了。有帮助吗?【参考方案7】:

如果有变化,我编写了这个脚本来跳过或执行测试:

#!/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

【讨论】:

【参考方案8】:

适用于 Bitbucket 存储库用户(以及其他使用源代码控制管理主机且 webhook 有效负载似乎未指示文件更改的人)。

似乎 Git 插件“包含的区域”无论我做什么都会失败,并且总是触发工作。我的设置是 Jenkins 2.268,在 Docker 容器中运行,要找到一种根据文件更改来实现构建作业的正确方法是炼狱,但下面是一个。

必需的 Jenkins 插件:

时髦 Bitbucket(或者,如果您在另一个 SCM 主机上:可以触发在该主机的 webhook 上构建的插件)

创建一个名为“Switch”的新 Freestyle 作业:

    源代码管理:指明您的 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)

我相信这种方法可以处理自上次构建以来的多次提交,因此它似乎满足了需求。欢迎任何改进建议。

【讨论】:

【参考方案9】:

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

How to get list of changed files since last build in 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;

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

【讨论】:

以上是关于仅当特定文件集发生更改时如何触发构建的主要内容,如果未能解决你的问题,请参考以下文章

如果 SharePoint 上的一个特定文件发生更改,则触发操作

如何排除对管道 yaml 文件的更改以触发构建 i azure devops?

仅当元素具有两个特定类时如何在元素上触发函数

仅当鼠标悬停在元素上至少特定时间时,如何触发 MouseMove 事件?

仅当行已更改时,MySQL 才在更新后触发

如何在 Rider 中触发文件更改的设计时构建?