1

Background: I'm a hardware designer trying to improve our verification methodology with Jenkins.

I've looked over a lot of questions here about Jenkinsfiles and groovy and include and none of them seem to get at my problem. I want to make things as easy as possible for the rest of my team, letting them specify in one simple data structure all of the jobs they want to run in this directory, for example:

def targets = [
  "Build number 1" : "make build",
  "Some other run command" : "custom_script"
]

My idea was to create a template Jenkinsfile for them to use that looked something like this:

def targets = [.....]
node {
   scm......

   load "common/Jenkinsfile"
}

and then common/Jenkinsfile to include the code to turn these targets into stages:

    try {
    targets.each {entry ->
        stage (entry.key) {
           sh "${complicated_command_prefix} $entry.value  ${complicated_command_suffix}"
        }
      }
    }
    catch (e) {
    emailext (
     // yadda yadda
            )
       throw e  
   }

This works if I place that code directly in my Jenkinsfile instead of the load command. But clearly load is doing something more sophisticated than a C preprocessor's #include, because the code doesn't work with load.

At first I get an error related to targets not being defined (because it's outside the scope of the loaded file presumably). Somebody suggested adding "env." as a prefix before targets, and that resolved the syntax error but no stages actually execute.

So...how can I properly pass "targets" to the loaded file so that the stages generated there are properly handled? Alternately, are there any other options to make this work better?

Edit: I've tried pasting my "def targets" directly into the loaded common file, and the stages get handled correctly. So the issue isn't that the loaded file doesn't do what it's supposed to, it's simply that it isn't receiving the value of "targets" from the parent Jenkinsfile.

Matt
  • 121
  • 5

2 Answers2

1

Here is a possible way this could be refactored while turning the implicit dependency to target into an explicit one, making the code easier to understand and maintain.

Template Jenkinsfile:

def targets = [.....]
node {
   scm......

   processTargets = load "common/Jenkinsfile"
   processTargets( targets )
}

common/Jenkinsfile:

void call( Map targets ) {
    try {
      targets.each {entry ->
        stage (entry.key) {
           sh "${complicated_command_prefix} $entry.value  ${complicated_command_suffix}"
        }
      }
    }
    catch (e) {
      emailext (
        // yadda yadda
      )
      throw e  
   }
}

// very important: return instance of the script class, which load() will return
return this  

By defining the standard call function, the object returned by load can be called like a function: processTargets( targets ). If you define differently named functions, you could call them like this:

common = load "common/Jenkinsfile"
common.myFunction1( foo )
common.myFunction2( bar )
zett42
  • 25,437
  • 3
  • 35
  • 72
  • This looks very helpful but I got my answer working quickly and even though it's messy and "bad", it's good enough for my needs. – Matt Sep 09 '20 at 17:09
0

Figured it out! I found this Strange variable scoping behavior in Jenkinsfile which explained that if I leave out def the variable will be visible externally. And it is. So I now define targets without def. Success!

Matt
  • 121
  • 5
  • Removing `def` you create an ugly global variable. I would define a function in "common/jenkinsfile" and pass `targets` as an explicit argument to make dependencies clear. – zett42 Feb 27 '20 at 21:59
  • Whoops never accepted an answer. Your answer is probably better but in this case I need a quick and dirty version and mine worked. – Matt Sep 09 '20 at 17:09