New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clarify what global variables really are and how they behave in Shared Libraries #494
Clarify what global variables really are and how they behave in Shared Libraries #494
Conversation
@jenkins-infra/copy-editors if you have some time |
=== Defining global variables | ||
|
||
Internally, scripts in the `vars` directory are instantiated as a singleton | ||
on-demand, when used first. So it is possible to define more methods, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"So it is possible" reads like a sentence fragment. Should it be something like "more methods or properties?"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/instantiated as a singleton on-demand, when used first./instantiated as a singleton. They created on-demand the first time they are called./
s/So it is possible/This makes it possible/
should be created and have a `call` method defined: | ||
For example, to define `helloWorld`, the file `vars/helloWorld.groovy` | ||
should be created and should implement a `call` method. The `call` method | ||
allows the global variable to be invoked in a manner similar to an step: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"similar to a step"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"allows a global"
Done with review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor issues/possible clarifications, but the methodality
needs to be fixed at least.
@@ -89,7 +89,7 @@ the use-case. _Manage Jenkins » Configure System » Global Pipeline Libraries_ | |||
as many libraries as necessary can be configured. | |||
|
|||
Since these libraries will be globally usable, any Pipeline in the system can | |||
utilize functionality implemented in these libraries. | |||
utilize methodality implemented in these libraries. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty sure this isn't a word.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hah, missed this in my manual diff after search/replace
=== Defining global variables | ||
|
||
Internally, scripts in the `vars` directory are instantiated as a singleton | ||
on-demand, when used first. So it is possible to define more methods, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird comma placement between methods
and properties
.
---- | ||
// vars/acme.groovy | ||
def setFoo(v) { | ||
this.foo = v; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a weird behavior in Pipeline scripts when variables/fields are the same name as the getter (resulting in stack overflows) – does this not apply when using this
, or when in a global library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verified that this does not result in stack overflows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daniel-beck http://docs.groovy-lang.org/latest/html/documentation/#properties - see the third example.
"this.name
will directly access the field because the property is accessed from within the class that defines it"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rtyler Thanks.
@bitwiseman Could be specific to scripts then, I saw some weird behavior in Pipeline that was different in standalone Groovy. Will need to look that up though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daniel-beck - for scripts, I totally believe you'll get an overflow, specifically you're not "in a class".
} | ||
---- | ||
|
||
Then the Pipeline Script can invoke these methods, rooted off the `acme` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"rooted off"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, "rooted off" is a weird verbing here
|
||
Shared Libraries can also define global variables which look and feel like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
look and feel -> behave?
|
||
For example, to define `helloWorld` step, the file `vars/helloWorld.groovy` | ||
should be created and have a `call` method defined: | ||
For example, to define `helloWorld`, the file `vars/helloWorld.groovy` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC they need to be camelCased rather than PascalCased – confirm, and if so, add a NOTE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirmed this, WEIRD! I didn't know that was an actual requirement. PascalCased results in the script not even compiling!
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
WorkflowScript: 2: unexpected token: Mr Jenkins @ line 2, column 14.
SayHello "Mr Jenkins"
|
||
[source,groovy] | ||
---- | ||
// vars/helloWorld.groovy | ||
def call(name) { | ||
// you can call any valid step functions from your code, just like you can from Pipeline scripts | ||
// Any valid steps can be called from this code, just like in other | ||
// Pipeline scripts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ambiguous wording given @NonCPS
not being able to do that despite being in a Pipeline script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the edge case you're describing as far as the @NonCPS
annotation goes, can you explain a bit more or link to something describing the issue more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're saying that steps can be called from here, "just like in other Pipeline scripts", however only the non-@NonCPS
parts of a Pipeline script can call steps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this document doesn't introduce the @NonCPS
annotation in any shape or form, I do not think this source snippet is the appropriate place to do so.
IMO we should find a way to introduce @NonCPS
in this section, in some form other than the most fitting title of "Crazy wacky shit you might run into"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daniel-beck Turns out somebody filed a ticket, WEBSITE-252, which captures the desire to have stuff like @NonCPS
documented.
I have assigned that to myself
==== | ||
The `call()` method will not be automatically invoked if `helloWorld` is | ||
referenced without *any* arguments. Implementing a global variable which | ||
requires no default arguments **must** be explicitly invoked like a method, for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIU this is the same as with any Groovy methods (even though this isn't a method), no? Maybe rephrase if that's the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what you're asking here. It would be the same with all Groovy objects. The global variable helloWorld
is just an object stuck into the scope of the Pipeline Script, and just referring to it like helloWorld
is just referencing a variable like any other
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think (AFAIK) any normal methods can be called as foo
either in Groovy. It's always foo()
, or foo "arg"
. If so, this explanation basically states "It's Groovy syntax, deal with it".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering that I have hit this behavior, and at least one other person hit it hard enough to file a JIRA about it, I think it's worth keeping a note in the documentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but the documentation reads like this is something very special and limited to steps, when it's simply not (AFAIK). Any parameterless method call needs parentheses (again, AFAIK).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace the example with:
def call(String name = 'Human') {
// Any valid steps can be called from this code, just like in other
// Pipeline scripts
echo "Hello, ${name}."
}
Calling example:
[source,groovy]
----
greetings 'Joe'
greetings()
----
This shows what you're noting with reiterating basic groovy language facts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the updated example you suggest @bitwiseman, with an inline code comment perhaps, does a decent job conveying things here.
I quite dislike the tone I'm perceiving in this review here though. There was a stumbling block which I hit (relatively savvy Pipeline user) and at least other user hit, hard enough to file a ticket.
Avoiding referencing or spelling out how "basic groovy language facts" work in Pipeline is a slightly more dressed up version of "lrn2groovy noob" in my opinion.
If users are hitting stumbling blocks, or having problems with Pipeline Script, that is a bug to me. Whether it's fixed in the UI, documentation, or Pipeline's syntax, something should be done to help future users avoid the same potholes.
That said, @bitwiseman are you planning on submitting a subsequent PR, or should I incorporate your updated examples here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rtyler -
Since you agree it serves the purpose. Please incorporate.
If users are hitting stumbling blocks, or having problems with Pipeline Script, that is a bug to me. Whether it's fixed in the UI, documentation, or Pipeline's syntax, something should be done to help future users avoid the same potholes.
I totally agree with you and I apologize for any tone issue.
The discussion (and subsequent style guideline) that needs to happen is "How do we give people the notes they need without repeatedly reminding readers about Groovy behavior?"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe having an appendix in the handbook that talks about Groovy pitfalls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A primer on Pipeline Groovy (Groovy but limited to what actually works and pointing out what fancy Groovy features don't work) would be an amazing reference for anyone trying to graduate past the "pure configuration DSL" kind of Pipeline scripts (loops, conditions, …) and people already familiar with Groovy and/or Java to get an idea what works and what doesn't to reduce trial/error. This way, people don't need to go through the progression from "Oh, coll.each { }
is broken", over "Oh, for (foo in coll)
is broken" to C-style loops.
This would also have the advantage of reducing the number of notes/warnings needed in all other parts of the Pipeline documentation.
Assuming this doesn't exist yet, of course. Haven't read all of the new Pipeline docs.
|
||
[source,groovy] | ||
---- | ||
// vars/jenkinsPlugin.groovy | ||
// vars/buildPlugin.groovy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove the jenkins
? Wouldn't that make the example clearer wrt what is being built here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to change this because I can link to the actual implementation of a global variable named buildPlugin once it's better documented and better code
ec88a88
to
9fc9df0
Compare
@daniel-beck Updated the latest commit with fixes. |
|
||
Internally, scripts in the `vars` directory are instantiated as a singleton | ||
on-demand, when used first. So it is possible to define more methods, | ||
properties on a single file that interact with each other: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ methods, properties on / methods and properties in /
---- | ||
// vars/acme.groovy | ||
def setFoo(v) { | ||
this.foo = v; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daniel-beck http://docs.groovy-lang.org/latest/html/documentation/#properties - see the third example.
"this.name
will directly access the field because the property is accessed from within the class that defines it"
def say(name) { | ||
echo "Hello world, ${name}" | ||
} | ||
---- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love this example. The text above talks about methods and properties in "a single file that interact with each other", but this example only shows the property getter and setter interacting, not the other method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then you can provide a new example in a pull request that you're more happy with 😈
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, it'll look like this:
// vars/acme.groovy
def setName(value) {
this.name = value;
}
def getName() {
return this.name;
}
def caution(message) {
echo "Hello, ${this.name}! CAUTION: ${message}"
}
Example:
acme.name = "Alice";
echo acme.name; // print "Alice"
acme.caution "The queen is angry!" // print "Hello, Alice. CAUTION: The queen is angry!"
==== | ||
A variable defined in a shared library will only show up in _Global Variables | ||
Reference_ (under _Pipeline Syntax_) after you have first run a successful | ||
build using that library, allowing its sources to be checked out by Jenkins. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ you have first run a successful build using that library, allowing its sources to be checked out by Jenkins. / Jenkins loads and uses that library as part of a successful build. /
should be created and have a `call` method defined: | ||
For example, to define `helloWorld`, the file `vars/helloWorld.groovy` | ||
should be created and should implement a `call` method. The `call` method | ||
allows the global variable to be invoked in a manner similar to an step: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"allows a global"
==== | ||
The `call()` method will not be automatically invoked if `helloWorld` is | ||
referenced without *any* arguments. Implementing a global variable which | ||
requires no default arguments **must** be explicitly invoked like a method, for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"A call
method may be defined with no arguments - def call() { ... }
. However, as with any Groovy method, when we invoke a method without arguments it must have empty parentheses after it. For example, helloWorld()
instead of helloWorld
."
…ies are just variables Basically, the end-user can pretend that they're just like other "steps" but in truth we're using some Groovy invocation magic which means parameterless invocation doesn't work in the same manner as a real step. Fixes JENKINS-39565
Instead of using metohds/functions interchangeably. This also removes any references to "global methods" and instead refers to those entities as "global variables" to ensure we avoid confusion around what those /things/ actually are.
9fc9df0
to
290d581
Compare
🐝 |
This should help explain behavior seen and reported in tickets like JENKINS-39565