2

我已经尝试了一段时间,开始努力将我们的自由风格项目转移到管道上。为此,我觉得最好建立一个共享库,因为我们的大多数构建都是相同的。我通读了 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”相同。奇怪的是,代码的脚本部分也不会使构建失败......所以不确定有什么问题。回声部分也显示为空。我究竟做错了什么?

4

1 回答 1

9

你的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'
}

让我们尝试调试一下。

如果您在方法中添加一个println(args),您将看到如下内容:body()call(body)

[emailTo:testuser@domain.com]

但是,只有一个值被设置。到底是怎么回事?

这里有几点需要理解:

  1. 设置 adelegate的作用是Closure什么?
  2. 为什么repositoryName:'my-git-repo'什么都不做?
  3. 为什么要emailTo='testuser@domain.com'在地图中设置属性?

设置 adelegate的作用是Closure什么?

这一个大多是直截了当的,但我认为它有助于理解。Closure功能强大,是 Groovy 的瑞士军刀。delegate本质上设置thisClosure. 您还使用了resolveStrategyof Closure.DELEGATE_FIRST,因此首先检查委托的方法和属性,然后检查封闭范围(所有者) - 请参阅Javadoc以获得深入的解释。如果您调用 , , 等方法size()put(...)它们entrySet()都会首先在delegate. 属性访问也是如此。

为什么repositoryName:'my-git-repo'什么都不做?

这可能看起来是一个Groovy 映射文字,但事实并非如此。这些实际上是标记语句。如果你用方括号将它括起来[repositoryName:'my-git-repo'],那将是一个地图文字。但是,这就是你要做的所有事情——创建一个地图文字。我们要确保这些对象在Closure

为什么要emailTo='testuser@domain.com'在地图中设置属性?

这是使用 Groovy 的map 属性表示法功能。如前所述,您已将 的 设置delegateClosuredef args= [:]Map. 您还设置了resolveStrategyof Closure.DELEGATE_FIRST。这使您emailTo='testuser@domain.com'决心被调用 on 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到目前为止,只能在共享库中定义整个s。这只能在 中完成vars/*.groovy,并且只能在一个call方法中完成。在一次构建中只能执行一个声明式管道,如果您尝试执行第二个,您的构建将因此失败。

于 2018-03-23T15:33:32.343 回答