Jenkinsfile 中奇怪的变量作用域行为
Strange variable scoping behavior in Jenkinsfile
当我 运行 下面的 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
,它可以正常工作。有人可以解释导致此行为的范围规则吗?
TL;DR
- 主脚本体中用
def
定义的变量无法从其他方法访问。
- 没有
def
定义的变量可以通过任何方法直接访问,甚至可以从不同的脚本访问。这是一个不好的做法。
- 定义的变量 with
def
and @Field
annotation 可以直接从相同定义的方法访问脚本。
说明
当 groovy 编译该脚本时,它实际上将所有内容移动到 class, 大致 看起来像这样
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 时,它看起来像这样
class Script1 {
def some_var = "some value"
def pr() {
def another_var = "another " + some_var
echo "${another_var}"
}
def run() {
//your pipeline
}
}
来自@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"
}
}
}
}
}
观察:
变量声明六例
一个。三种类型(带 def、不带 def、带 def 和带 @field)before/above pipeline
b。在脚本管道内(有 def,没有 def)在管道内
c。局部于管道外的方法(带 def)
新的变量声明和重新分配需要管道内的脚本管道。
所有在流水线外声明的变量都可以在阶段之间访问
带有 def 关键字的变量通常特定于方法,如果它在脚本内部声明,则在脚本外部将不可用。所以需要在脚本内声明全局变量(不带def)才能在脚本外访问。
当我 运行 下面的 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
,它可以正常工作。有人可以解释导致此行为的范围规则吗?
TL;DR
- 主脚本体中用
def
定义的变量无法从其他方法访问。 - 没有
def
定义的变量可以通过任何方法直接访问,甚至可以从不同的脚本访问。这是一个不好的做法。 - 定义的变量 with
def
and@Field
annotation 可以直接从相同定义的方法访问脚本。
说明
当 groovy 编译该脚本时,它实际上将所有内容移动到 class, 大致 看起来像这样
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 时,它看起来像这样
class Script1 {
def some_var = "some value"
def pr() {
def another_var = "another " + some_var
echo "${another_var}"
}
def run() {
//your pipeline
}
}
来自@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"
}
}
}
}
}
观察:
变量声明六例
一个。三种类型(带 def、不带 def、带 def 和带 @field)before/above pipeline
b。在脚本管道内(有 def,没有 def)在管道内
c。局部于管道外的方法(带 def)
新的变量声明和重新分配需要管道内的脚本管道。
所有在流水线外声明的变量都可以在阶段之间访问
带有 def 关键字的变量通常特定于方法,如果它在脚本内部声明,则在脚本外部将不可用。所以需要在脚本内声明全局变量(不带def)才能在脚本外访问。