根据对子文件夹的更改触发 Azure DevOps 构建
Posted
技术标签:
【中文标题】根据对子文件夹的更改触发 Azure DevOps 构建【英文标题】:Triggering Azure DevOps builds based on changes to sub folders 【发布时间】:2019-04-13 02:50:57 【问题描述】:我有一个包含多个项目的 Visual Studio 解决方案,每个项目都是一个单独的微服务。对于开发团队来说,将所有服务都放在同一个解决方案和 git repo 中非常方便,因为服务可以相互调用。
Master.sln - SubFolderA - MicroserviceA.sln
- SubFolderB - MicroserviceB.sln
- SubFolderC - MicroserviceC.sln
但是,我希望在 Azure DevOps 中的各个微服务发生更改时独立构建/发布它们,因此如果 ServiceA 是唯一要更改的服务,那么 ServiceA 是唯一构建和部署的服务。
为此,我创建了一个新的构建管道定义,其中设置了“路径过滤器”以在微服务文件夹的内容更改时触发构建(因此每个微服务添加一个路径过滤器以进行监控)。
我的问题是,当触发构建时(例如基于对 SubFolderA 的更改),我无法告诉构建定义仅在 SubFolderA 中构建 .sln 文件。
我可以为每个微服务创建一个单独的构建定义,并在单独的子文件夹上触发每个构建,但这会产生很大的开销,即我需要维护 15 个单独的构建定义(对于我构建的每个分支也一样),并且我们的自托管构建代理所需的存储现在将是 NumberOfService x NumberOfBranchesBeingBuild x SizeOfRepo。
有没有办法使用带有 git“路径过滤器”和定义的多个路径的单个构建定义,这反过来会启动多个构建实例并将触发构建的路径的值提供给构建定义等等要构建哪个 .sln 文件的构建实例?
我希望这是有道理的!
【问题讨论】:
> 因为服务可以相互调用。 直接相互调用(例如通过 HTTP/S 等),而是通过消息总线 .... 【参考方案1】:解决方案是在子文件夹中为每个服务设置一个 azure-pipelines.yml。子文件夹中的每个 azure-pipelines.yml 都必须有以下触发器定义。
trigger:
branches:
include:
- master
paths:
include:
- <service subfolder name>/*
yaml 的路径 -> 包含部分,告诉 azure 管道仅在该特定路径发生更改时触发。
子文件夹中的 azure-pipelines.yml 无需另外命名,可以保留相同的名称。同样,没有必要将 azure-pipelines.yml 添加到 repo 的根目录,除非有一些代码需要在子文件夹之外的根目录中构建。在这种情况下,必须将以下触发器部分添加到 repo 根目录下的 azure-pipelines.yml。
trigger:
branches:
include:
- master
paths:
exclude:
- <service subfolder name 1>/*
- <service subfolder name 2>/*
paths -> exclude 部分排除了已经包含自己的 azure-pipelines.yml 文件的子文件夹,并且仅当子文件夹之外的 repo 的根目录发生更改时才会触发。
blog 更详细地解释了 monorepo 管道。
【讨论】:
【参考方案2】:为了补充 deleb 的回答,这里是设置路径触发器的 YAML 代码:
trigger:
branches:
include:
- master
paths:
include:
- /path/to/src*
请注意,您还需要有分支触发器才能使用路径触发器。
https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/azure-repos-git?view=azure-devops&tabs=yaml#paths
【讨论】:
【参考方案3】:这篇文章对我帮助很大,所以我想添加一些我对我的流程所做的有用修改。
我发现的第一个主要问题是这个 git diff 命令不能一次处理多个提交。
git diff HEAD HEAD~ --name-only
HEAD~ 只查看 1 次提交,因为一次推送可能同时包含多个提交。
我意识到我需要在 HEAD 和 自管道上次成功运行以来的提交 ID 之间进行差异。
git diff HEAD [commit id of last successful build] --name-only
可通过在 /build/latest 端点 sourceVersion 调用 Azure DevOps API 来获得此提交 ID。
$response = (Invoke-RestMethod -Uri $url -Method GET -Headers $AzureDevOpsAuthenicationHeader)
$editedFiles = (git diff HEAD $response.sourceVersion --name-only)
我还对查找更改的项目/模块文件夹的逻辑进行了修改。我不想每次通过硬编码项目名称来添加新项目时都必须修改我的 PowerShell 脚本。
$editedFiles | ForEach-Object
$sepIndex = $_.IndexOf('/')
if($sepIndex -gt 0)
$projectName = $_.substring(0, $sepIndex)
AppendQueueVariable $projectName
AppendQueueVariable 将维护所有已更改项目的列表以返回管道。
最后,我获取排队项目的列表并将它们传递到我的 Maven 多模块构建管道任务中。
mvn -amd -pl [list returned from PS task] clean install
【讨论】:
最佳答案在这里。现在在我们的项目中做同样的事情。 尝试使用此 git diff 命令,但发现如果我在 master 分支上重新运行管道,则会出现错误(第一次 PR 合并到 master 工作,但是当有没有改变,失败了)我得到的错误是:##[error]warning: refname 'bb3e659a02a130210f4dc7571d03a3a6693f169b' is ambiguous.
能否请您扩展一下,This commit id is available by calling the Azure DevOps API at the /build/latest endpoint, sourceVersion.
我尝试了几个端点并查看了文档,但无法确定确切的网址。
我在 API 上使用名为 /build/latest/ 的端点 这是文档的链接:docs.microsoft.com/en-us/rest/api/azure/devops/build/latest/… 此端点将为您提供一个名为 sourceVersion 的元素的响应。这将是此过程所需的提交 ID。【参考方案4】:
在 bash 中,您可以执行以下操作:
- task: Bash@3
displayName: 'Determine which apps were updated'
inputs:
targetType: 'inline'
script: |
DIFFS="$(git diff HEAD HEAD~ --name-only)"
[[ "$DIFFS[@]" =~ "packages/shared" ]] && echo "##vso[task.setvariable variable=SHARED_UPDATED]True"
[[ "$DIFFS[@]" =~ "packages/mobile" ]] && echo "##vso[task.setvariable variable=MOBILE_UPDATED]True"
[[ "$DIFFS[@]" =~ "packages/web" ]] && echo "##vso[task.setvariable variable=WEB_UPDATED]True"
【讨论】:
您需要添加 set +e,因为如果文件未更改,则测试将返回退出代码 1,并且任务将在 Azure Pipelines 中失败【参考方案5】:在“触发器”选项卡上,有一个选项可以指定要构建的项目的路径。指定该路径后,只有包含与包含/排除规则匹配的修改的提交才会触发构建。
在我的情况下,这是一个比 PowerShell 脚本更好的解决方案,它仍然会触发所有项目的构建和发布,向我们的 Slack 发送垃圾邮件并填充我们项目历史的垃圾。
【讨论】:
这确实是最优雅的解决方案。但是,应该注意的是,非云托管源不支持路径过滤器。因此,如果您有Azure Repos Git
、GitHub
、Bitbucket Cloud
等作为您的来源,您可以使用上面的解决方案。但是,如果您使用 Other Git
选项作为源(像我一样),那么很遗憾,上述解决方案不可用。
无论您添加多少个路径过滤器,它仍然会构建整个解决方案而不是指定的解决方案,对吗?如果是这样,那将无法满足用户的要求,即在一个 repo 中构建多个解决方案中的一个。
我相信它只会构建指定的项目。尽管该项目可能与解决方案中的其他项目有依赖关系,但在这种情况下,它们也会被构建。
@Greg 是正确的。如果我设置了 3 个路径过滤器(libraryA、libraryB 和 libraryC),每次对这些文件夹中的 any 进行更改时,都会重新构建所有三个。我相信 OP 只想重建实际发生变化的那个。所以这个解决方案实际上不会做 OP 所要求的......
这个解决方案只是解释了触发器。您还可以在实际任务中指定正在恢复、构建、发布等项目。【参考方案6】:
Jayendran 的回答非常好!这是执行步骤 2 的更 PowerShell-y 方式:
$editedFiles = git diff HEAD HEAD~ --name-only
$editedFiles | ForEach-Object
Switch -Wildcard ($_ )
'SubFolderA/*' Write-Output "##vso[task.setvariable variable=MicroserviceA]True"
# The rest of your path filters
【讨论】:
我注意到的一件事是,如果您一次推送多个提交,管道只会考虑最近的提交。因此,如果我编辑三个不同的项目并为每个项目单独提交,则此脚本将仅评估最近的提交,从而导致仅构建一个项目。有没有办法编辑脚本或管道以针对所有推送的提交运行 git diff?【参考方案7】:你可以像下面那样做
-
根据您的微服务创建变量,值为“False”
例如,MicroserviceAUpdated
= "False"、MicroserviceBUpdated
= "False" 等,
-
在构建定义的开头添加一个 Powershell 脚本任务。
powershell 脚本将执行以下操作:
在构建中获取变更集/提交以检查哪些文件被更改。
将MicroserviceAUpdated
变量更新为“true”(如果有)
SubFolderA
下的文件已更改。
将MicroserviceBUpdated
变量更新为“true”(如果有)SubFolderA
下的文件已更改。
等等……
-
为每个微服务创建单独的构建任务,配置构建任务以使用如下自定义条件运行
对于微服务构建任务
“自定义条件”:
and(succeeded(), eq(variables['MicroserviceAUpdated'], 'True'))
对于微服务B构建任务
“自定义条件”:
and(succeeded(), eq(variables['MicroserviceBUpdated'], 'True'))
等等……
这样,如果变量的值为False
,则MicoserviceTask会被跳过
第 2 步
$files=$(git diff HEAD HEAD~ --name-only)
$temp=$files -split ' '
$count=$temp.Length
echo "Total changed $count files"
For ($i=0; $i -lt $temp.Length; $i++)
$name=$temp[$i]
echo "this is $name file"
if ($name -like "SubFolderA/*")
Write-Host "##vso[task.setvariable variable=MicroserviceAUpdated]True"
【讨论】:
听起来很有希望,在第 2 步中,您知道 git 命令是什么来确定文件夹是否已更改然后将构建变量设置为 true 吗? @Slicc 我已经用代码更新了我的答案。这样,最新的提交就包含了所有的更改。因此,请确保您拥有最新提交中的所有更改。 如果这样做,你如何处理项目依赖关系?假设你有 SubFolderCommon – MathLib.sln,微服务 A、B、K 和 F 依赖于 @user1038502 如果对SubFolderCommon
的更改改变了微服务(MS),MS 是否也会更新以适应更改(这会触发他们自己的构建)?如果 MS 依赖于 SubFolderCommon
的输出,那么他们不会通过重新发布 MathLib.sln
来继续工作吗?
powershell 脚本是否仅适用于存储在 GIT 中的代码?还是可以使用存储在 TFS 中的代码?以上是关于根据对子文件夹的更改触发 Azure DevOps 构建的主要内容,如果未能解决你的问题,请参考以下文章
从 Azure DevOps 中的子模块触发父存储库中的构建
如何通过 SSH 在 azure devops 管道复制文件中使用公钥