= Self-wrapping code()
I have my own version of "declarative" we use, and in that case you can add parameters like timestamps: true
so I have code roughly (see below) like:
def workflow = { // stuff to do }
if (config.timestamps){
//*******NEEDED *****//
def inner = workflow()
//********************//
workflow = { timestamps { inner() } }
}
// other possible wrappers
echo "Starting workflow"
workflow()
scope is important, if same payload variable is used, it is not evaluated until payload is called, which may end up in trouble - your best bet is to make sure the scope is isolated for each call
// this should isolate payload for each call - so that you do not have collisions/circular references
def wrapWithMatrix(matrixTag, userBody){
def payload = { userBody.call() }
if (matrixTag != null) {
return { script.withEnv([matrixTag]) { payload() } }
}
return payload
}
If all is needed in one go, temporary vars can help avoid infinite recursion (see inside vs perform):
// Wrap with timestamps, if necessary and available
if (config.timestamps) {
if (validStep('timestamps')) {
debug 'enabling timestamps'
def inside = perform
perform = {
steps.timestamps {
return inside()
}
}
} else {
steps.echo 'WARNING: timetamps are requested but plugin is missing, not enabling'
}
} else {
debug 'timestamps are requested, not plugin is not installed'
}
== Running in node... or not:
- i.e.
....
// Note: named closure, not method
def content = {
// execute code
}
if (condition) {
node("label&&label2"){
content()
} else{
content()
}
}
so in your case if you want to simplify things:
def withOptionalNode(label, closure code){
if (label){
code = {
node(label){
code()
}
}
code()
}
Also add an overload:
def withOptionalNode(Closure code){
withOptionalNode(null, code)
}
now you can just use
...
withOptionalNode {
...
}
withOptionalNode(label) {
...
}
and it will allocate node if label var has content and not if not
- BTW, it occurs to me that even simpler version of withOptionalNode would be (together with overload):
/**
* Run code with optional node allocation if label is specified
**/
def withOptionalNode(label, Closure code) {
return label ? node(label, code) : code()
}
/**
* wrapper for `withOptionalNode` without label provided
**/
def withOptionalNode(Closure code) {
return withOptionalNode(null, code)
}
== Populating parallel blocks
def myParallelTasks = [
task1: { ..... },
task2: { .... },
...
failFast: false
]
...with generated closure contents:
i.e.
def makeContent(param1, param2){
def computeParam3 = ...
return {
echo "Param1: ${param1}"
echo "Param2: ${param2}"
echo "computeParam3: ${computeParam3}"
}
}
...
def parts = [
part1: makeContent("1", "2"),
part2: makeContent("3", "4"),
]
and also
part3: {
node("bob"){
makeContent("bob", "5").call()
}
}
== Missing parameters
btw, should groovy fail or not for access to map entry by name that is not defined there? :)
groovy will return null if parameter not present in map and fail if map is null - but you can protect yourself:
Map map = [a:'b']
assert map.a == 'b'
map = []
assert map.a == null
map = null
// this with throw an exception
assert map.a != null
// this will work
assert map?.a == null
this can fail: map.a.lower()
so will this map?.a.lower()
however this will return null: map?.a?.lower()
you can use elvis to have a non-null return too, i.e. echo "${map?.a?.lower() ?: '')"
will not print null
if a
is missing
- Idea on Separating reuse of same closure with unique args so parallels do not step on each other's toes:
def makeAction(arg){
return { ->
echo "Something that uses ${arg}"
}
}
parallel [
one: makeAction('one'),
two: makeAction('two'),
]
- i.e. instead of
def action = { x -> ... }
either do
def action(x){
...
}
or, if you really want it to be a closure
def makeAction(){
return {x -> ... }
}
- on methods vs closures... something like that:
parallel [
one: { action('one') }, // if action is a method and not closure
two: { makeAction()('two') } // if makeAction returns a closuire
]
- for scripted it is same as parallel, but instead of
parallel
you do something like
stages.each { name, closure ->
stage(name){
closure()
}
}
though you may or may not have an issue with order, you can use list instead of map for closures to maintain order
- Parallel with "quiet" failfast equivalent: let running stages complete, do not start new ones
//in the beginning
boolean failed=false
def stageWrapper(Closure action){
if (! failed){
try {
action()
} catch (exception ex) {
failed = true
//handle exception - or just re-throw it
}
}
}
parallel([
one: { stageWrapper{
// your code
},
two: { stageWrapper{
// your code
}
}
])
- Wrapping default/custom closures for generic code
// vars/lockableDB.groovy
def onprem(Map options=[:]){
def config = [
setidCloneDBFunc: { param -> return "default closure with ${param}" },
param: 'default value'
] << options ?: [:]
//code
echo "${config.setidCloneDBFunc(config.param)}"
}
and users call it as:
lockableDB.onperm()
or
lockableDB.onprem setidCloneDBFunc: { param ->
return "custom closure with ${param}"
}
}
NOTE: it's an example, not exact solution
If you want an object/class code, you can place it behind the var implementation
This sort of an approach makes it more DSL-y on the enduser, similar to declarative approach - but works either way. Also removes the need for imports and other complex code things.