我已经尝试了一段时间,开始努力将我们的自由风格项目转移到管道中。为此,我觉得最好建立一个共享库,因为我们的大多数构建都是相同的。我通读了 Jenkins 的这篇博文。我想出了以下内容
// vars/buildGitWebProject.groovy
def call(body) {
def args= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = args
body()
pipeline {
agent {
node {
label 'master'
customWorkspace "c:\jenkins_repos\${args.repositoryName}\${args.branchName}"
}
}
environment {
REPOSITORY_NAME = "${args.repositoryName}"
BRANCH_NAME = "${args.branchName}"
SOLUTION_NAME = "${args.solutionName}"
}
options {
buildDiscarder(logRotator(numToKeepStr: '3'))
skipStagesAfterUnstable()
timestamps()
}
stages {
stage("checkout") {
steps {
script{
assert REPOSITORY_NAME != null : "repositoryName is null. Please include it in configuration."
assert BRANCH_NAME != null : "branchName is null. Please include it in configuration."
assert SOLUTION_NAME != null : "solutionName is null. Please include it in configuration."
}
echo "building with ${REPOSITORY_NAME}"
echo "building with ${BRANCH_NAME}"
echo "building with ${SOLUTION_NAME}"
checkoutFromGitWeb(args)
}
}
stage('build and test') {
steps {
executeRake(
"set_assembly_to_current_version",
"build_solution[$args.solutionName, Release, Any CPU]",
"copy_to_deployment_folder",
"execute_dev_dropkick"
)
}
}
}
post {
always {
sendEmail(args)
}
}
}
}
在我的管道项目中,我将管道配置为使用管道脚本,脚本如下:
buildGitWebProject {
repositoryName:'my-git-repo'
branchName: 'qa'
solutionName: 'my_csharp_solution.sln'
emailTo='testuser@domain.com'
}
我已经尝试过使用和不使用环境块,但结果最终与每个参数的值为"null"相同。奇怪的是,代码的脚本部分也不会使构建失败......所以不知道这有什么问题。回声部分也显示空。我做错了什么?
你的Closure
身体没有按照你期望/相信的方式行事。
在方法开始时,您有:
def call(body) {
def args= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = args
body()
您的呼叫正文是:
buildGitWebProject {
repositoryName:'my-git-repo'
branchName: 'qa'
solutionName: 'my_csharp_solution.sln'
emailTo='testuser@domain.com'
}
让我们试一试调试一下。
如果在call(body)
方法的body()
后添加println(args)
,您将看到如下所示的内容:
[emailTo:testuser@domain.com]
但是,只设置了一个值。这是怎么回事?
这里有几件事需要了解:
- 设置
Closure
的delegate
有什么作用? - 为什么
repositoryName:'my-git-repo'
什么都不做? - 为什么
emailTo='testuser@domain.com'
在地图上设置属性?
设置
Closure
的delegate
有什么作用?
这个大多是直截了当的,但我认为它有助于理解。Closure
很强大,是Groovy的瑞士军刀。delegate
基本上设置了Closure
体内的this
。您还使用了Closure.DELEGATE_FIRST
的resolveStrategy
,因此首先检查委托的方法和属性,然后检查封闭范围(所有者)的方法和属性 - 有关深入说明,请参阅 Javadoc。如果调用像size()
、put(...)
、entrySet()
等方法,它们都是首先在delegate
上调用的。物业访问也是如此。
为什么
repositoryName:'my-git-repo'
什么都不做?
这可能看起来像是Groovy地图的文字,但事实并非如此。这些实际上是标记的语句。如果你用方括号括起来,比如[repositoryName:'my-git-repo']
,那么这将是一个地图文字。但是,这就是您在那里要做的全部工作 - 就是创建一个地图文字。我们希望确保这些对象在Closure
为什么
emailTo='testuser@domain.com'
在地图上设置属性?
这是使用 Groovy 的地图属性表示法功能。如前所述,您已将Closure
的delegate
设置为def args= [:]
,这是一个Map
。您还设置了Closure.DELEGATE_FIRST
的resolveStrategy
。这使您的emailTo='testuser@domain.com'
决心在args
上被调用,这就是将emailTo
键设置为值的原因。这相当于调用args.emailTo='testuser@domain.com'
。
那么,你如何解决这个问题?
如果要保留Closure
语法方法,可以将调用正文更改为实质上将值存储在委托args
映射中的任何内容:
buildGitWebProject {
repositoryName = 'my-git-repo'
branchName = 'qa'
solutionName = 'my_csharp_solution.sln'
emailTo = 'testuser@domain.com'
}
buildGitWebProject {
put('repositoryName', 'my-git-repo')
put('branchName', 'qa')
put('solutionName', 'my_csharp_solution.sln')
put('emailTo', 'testuser@domain.com')
}
buildGitWebProject {
delegate.repositoryName = 'my-git-repo'
delegate.branchName = 'qa'
delegate.solutionName = 'my_csharp_solution.sln'
delegate.emailTo = 'testuser@domain.com'
}
buildGitWebProject {
// example of Map literal where the square brackets are not needed
putAll(
repositoryName:'my-git-repo',
branchName: 'qa',
solutionName: 'my_csharp_solution.sln',
emailTo: 'testuser@domain.com'
)
}
另一种方法是让您的call
将Map
作为参数并删除您的Closure
。
def call(Map args) {
// no more args and delegates needed right now
}
buildGitWebProject(
repositoryName: 'my-git-repo',
branchName: 'qa',
solutionName: 'my_csharp_solution.sln',
emailTo: 'testuser@domain.com'
)
还有一些其他方法可以对 API 进行建模,这取决于您要提供的 UX。
有关共享库代码中声明性管道的旁注:
值得记住的是共享库中声明性管道的限制。看起来您已经在vars
中这样做了,但我只是为了完整起见在此处添加它。在文档的最后,它指出:
截至目前,只能在共享库中定义整个
pipeline
。这只能在vars/*.groovy
中完成,并且只能在call
方法中完成。在单个生成中只能执行一个声明性管道,如果尝试执行第二个声明性管道,则生成将因此失败。