Jenkinsfile中奇怪的变量范围行为

时间:2018-05-28 17:44:25

标签: jenkins jenkins-pipeline jenkins-groovy

当我运行下面的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

如果def已从some_var中移除,则可以正常使用。有人可以解释导致此行为的范围规则吗?

2 个答案:

答案 0 :(得分:22)

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为所有脚本共享相同的Binding。更好的选择是使用@Field 注释。这样,您可以在一个脚本中的所有方法中访问变量,而不会将其暴露给其他脚本。

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
    }
}

答案 1 :(得分:0)

@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. 六种变量声明情况

    a。管道之前/之上的三种类型(带有def,没有def,带有def和@field)

    b。在管道内的脚本管道内(使用def,不使用def)

    c。管道外部方法(使用def)的本地

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

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

  4. 通常使用def关键字来变量,该关键字通常特定于方法,如果在脚本中声明了该方法,则在该方法之外将不可用。因此需要在脚本中声明全局变量(不带def),以便在脚本外部进行访问。