【问题标题】:Strange variable scoping behavior in JenkinsfileJenkinsfile 中奇怪的变量作用域行为
【发布时间】:2018-05-28 17:44:25
【问题描述】:

当我运行以下 Jenkins 管道脚本时:

def some_var = "some value"

def pr() {
    def another_var = "another " + some_var
    echo "${another_var}"
}

pipeline {
    agent any

    stages {
        stage ("Run") {
            steps {
                pr()
            }
        }
    }
}

我收到此错误:

groovy.lang.MissingPropertyException: No such property: some_var for class: groovy.lang.Binding

如果从some_var 中删除def,它可以正常工作。有人可以解释导致这种行为的范围规则吗?

【问题讨论】:

    标签: jenkins groovy jenkins-pipeline jenkins-groovy groovyshell


    【解决方案1】:

    TL;DR

    • 在主脚本正文中 def 定义的变量无法从其他方法访问。
    • 定义的变量没有 def 可以通过任何方法直接访问,甚至可以从不同的脚本访问。这是一种不好的做法。
    • def @Field 注释定义的变量可以直接从同一脚本中定义的方法访问。

    说明

    当 groovy 编译该脚本时,它实际上会将所有内容移动到一个大致看起来像这样的类

    class Script1 {
        def pr() {
            def another_var = "another " + some_var
            echo "${another_var}"
        }
        def run() {
            def some_var = "some value"
            pipeline {
                agent any
                stages {
                    stage ("Run") {
                        steps {
                            pr()
                        }
                    }
                }
            }
        }
    }
    

    您可以看到some_var 显然超出了pr() 的范围,因为它是不同方法中的局部变量。

    当您没有 def 定义变量时,您实际上将该变量放入脚本的Binding 中(所谓的绑定变量)。因此,当 groovy 执行 pr() 方法时,它首先尝试查找名称为 some_var 的局部变量,如果它不存在,则尝试在 Binding 中找到该变量(它存在是因为您在没有 def 的情况下定义了它)。

    绑定变量被认为是不好的做法,因为如果您加载多个脚本(load 步骤),所有这些脚本中都可以访问绑定变量,因为 Jenkins 为所有脚本共享相同的绑定。一个更好的选择是使用@Field annotation。这样一来,您就可以在一个脚本内的所有方法中访问一个变量,而无需将其暴露给其他脚本。

    import groovy.transform.Field
    
    @Field 
    def some_var = "some value"
    
    def pr() {
        def another_var = "another " + some_var
        echo "${another_var}"
    }
    //your pipeline
    

    当 groovy 将此脚本编译成一个类时,它看起来像这样

    class Script1 {
        def some_var = "some value"
    
        def pr() {
            def another_var = "another " + some_var
            echo "${another_var}"
        }
        def run() {
            //your pipeline
        }
    }
    

    【讨论】:

    • 感谢您详细解释生成脚本的可视化。也感谢@Field 的建议。你的回答出乎我的意料!
    • 很好的答案,在并行阶段内声明变量(带有{out} def 和@field)是否有从一个阶段到另一个阶段的访问限制?在管道脚本的阶段/步骤中声明的变量的可见性如何?
    • 还请解释一下在环境块中声明的变量的范围/可访问性,以及它如何等效于java类变量?
    【解决方案2】:

    来自@Vitalii Vitrenko 的精彩回答!
    我试图通过程序来验证这一点。还添加了一些测试用例。

    import groovy.transform.Field
    
    @Field  
    def CLASS_VAR = "CLASS"
    def METHOD_VAR = "METHOD"
    GLOBAL_VAR = "GLOBAL"
    
    def testMethod() {
        echo  "testMethod starts:" 
        def testMethodLocalVar = "Test_Method_Local_Var"
        testMethodGlobalVar = "Test_Metho_Global_var"
        echo "${CLASS_VAR}"
        // echo "${METHOD_VAR}" //can be accessed only within pipeline run method
        echo "${GLOBAL_VAR}"
        echo "${testMethodLocalVar}"
        echo "${testMethodGlobalVar}"
        echo  "testMethod ends:" 
    }
    
    pipeline {
        agent any
        stages {
             stage('parallel stage') {
                 parallel {
                     stage('parallel one') {
                         agent any
                         steps {
                            echo  "parallel one" 
                            testMethod()
                            echo "${CLASS_VAR}"
                            echo "${METHOD_VAR}"
                            echo "${GLOBAL_VAR}"
                            echo "${testMethodGlobalVar}"
                            script {
                                pipelineMethodOneGlobalVar = "pipelineMethodOneGlobalVar"
                                sh_output = sh returnStdout: true, script: 'pwd' //Declared global to access outside the script
                            }
                            echo "sh_output ${sh_output}"
                         }
                     }
                     stage('parallel two') {
                         agent any
                         steps {
                             echo  "parallel two"
                            //  pipelineGlobalVar = "new"      //cannot introduce new variables here
                            //  def pipelineMethodVar = "new"  //cannot introduce new variables here
                             script { //new variable and reassigning needs scripted-pipeline
                                 def pipelineMethodLocalVar = "new";
                                 pipelineMethodLocalVar = "pipelineMethodLocalVar reassigned";
                                 pipelineMethodGlobalVar = "new" //no def keyword
                                 pipelineMethodGlobalVar = "pipelineMethodGlobalVar reassigned"
    
                                 CLASS_VAR = "CLASS TWO"
                                 METHOD_VAR = "METHOD TWO"
                                 GLOBAL_VAR = "GLOBAL TWO"
                             }
                            //  echo "${pipelineMethodLocalVar}" only script level scope, cannot be accessed here
                             echo "${pipelineMethodGlobalVar}"
                             echo "${pipelineMethodOneGlobalVar}"
                             testMethod()
                         }
                     }
                 }
             }
             stage('sequential') {
                 steps {
                     script {
                         echo "sequential"
                     }
                 }
             }
         }
    }
    

    观察:

    1. 变量声明六例

      一个。管道之前/之上的三种类型(带def、不带def、带def和带@field)

      b.在脚本管道内(有def,没有def)在管道内

      c。局部于管道外的方法(带 def)

    2. 新变量声明和重新分配需要管道内的脚本管道。

    3. 在管道之外声明的所有变量都可以在阶段之间访问

    4. 带有 def 关键字的变量通常特定于方法,如果在脚本内部声明,则在其外部将不可用。所以需要在脚本内声明全局变量(不带def)才能在脚本外访问。

    【讨论】:

    • 谢谢,也很棒!在我的情况下,詹金斯在使用@Field 时抛出。我已将带有def 的变量从管道传递到外部方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-05
    • 1970-01-01
    • 2021-10-27
    • 2014-06-05
    • 2017-08-06
    • 1970-01-01
    相关资源
    最近更新 更多