85

I have a jenkinsfile dropped into the root of my project and would like to pull in a groovy file for my pipeline and execute it. The only way that I've been able to get this to work is to create a separate project and use the fileLoader.fromGit command. I would like to do

def pipeline = load 'groovy-file-name.groovy'
pipeline.pipeline()
Michael Lihs
  • 7,460
  • 17
  • 52
  • 85
user301693
  • 2,377
  • 7
  • 22
  • 24

6 Answers6

131

If your Jenkinsfile and groovy file in one repository and Jenkinsfile is loaded from SCM you have to do:

Example.Groovy

def exampleMethod() {
    //do something
}

def otherExampleMethod() {
    //do something else
}
return this

JenkinsFile

node {
    def rootDir = pwd()
    def exampleModule = load "${rootDir}@script/Example.Groovy "
    exampleModule.exampleMethod()
    exampleModule.otherExampleMethod()
}
Ivancho
  • 141
  • 3
  • 14
Anton Shishkin
  • 1,696
  • 1
  • 8
  • 10
  • 115
    Readers, note that in the Groovy, "return this" is crucial. – Michael Easter Jan 21 '17 at 15:29
  • 1
    @anton is it also possible to call methods from the loaded script without using ```example.``` all the time? Furthermore one might want that the loaded script can read and write variables from the outer script. Is that possible? – nepa Jan 24 '17 at 15:28
  • @nepa no, it's not possible. In order to work with variables, you must specify the method arguments – Anton Shishkin Jan 25 '17 at 07:27
  • 2
    @anton well in the meantime i found the ```.&``` operator (e.g.: ```def exampleMethod = example.&exampleMethod```. that works pretty well... – nepa Jan 29 '17 at 16:40
  • @MichaelEaster yeah, I missed that a bunch. And, important: It's *outside* of the function scope – Christian Bongiorno Jul 19 '17 at 00:14
  • 2
    @MichaelEaster what exactly 'this' returns? If that would be gradle script, it would return an instance of a Project class as the binding. But in a plain groovy file, I couldn't figure that. – stdout Jan 04 '18 at 12:51
  • 1
    I get `NoSuchFileException` – Saikat Feb 17 '19 at 16:05
  • 3
    @AntonShishkin , What is the `${rootDir}` variable, and where/how is it set? Is it intrinsic to Jenkins or custom? I am getting `groovy.lang.MissingPropertyException: No such property: rootDir for class: groovy.lang.Binding`. – cowlinator Apr 11 '19 at 20:24
  • 2
    Ok, so this is outdated. Instead of `${rootDir}`, use `${workspace}`, which is set by Jenkins. Also, be sure to uncheck "Lightweight checkout" in the pipeline job's configuration, or else your `.groovy` script(s) will not be downloaded from SCM. – cowlinator Apr 11 '19 at 20:33
  • 3
    So, I found a caveat -- when running 2 concurrent instances of the same job, Jenkins will append `@2` to the workspace name... however, it does not to so for the `@script` directory, meaning that `${workspace}@script/Example.Groovy` will not work when running concurrent builds. – cowlinator Apr 19 '19 at 21:47
  • I found a solution -- use `${jenkins_home}/workspace/${job_name}@script/Example.Groovy` instead. – cowlinator Apr 19 '19 at 22:30
  • 2
    What is declarative style equivalent for this? – Kanagavelu Sugumar Nov 21 '19 at 16:50
  • Please note that in case the methods called on `exampleModule` will come with their own node block(s) and/or do not need a node block at all, they really should not be called from within the node block loading `Example.Groovy`. – Joerg S Feb 25 '22 at 17:20
  • 2
    I have a path to the pulled files including groovy (let's say it's in the root of the project) like `workspace/Job Name@script/ecb7a9317b1ad6......../Example.groovy` How can I determine or get rid of that additional automatically added folder with long `ecb7a9317b1ad6........` name? – bodich Apr 10 '22 at 17:48
  • Please tag as correct answer – enanone Aug 29 '22 at 09:11
34

If you have pipeline which loads more than one groovy file and those groovy files also share things among themselves:

JenkinsFile.groovy

def modules = [:]
pipeline {
    agent any
    stages {
        stage('test') {
            steps {
                script{
                    modules.first = load "first.groovy"
                    modules.second = load "second.groovy"
                    modules.second.init(modules.first)
                    modules.first.test1()
                    modules.second.test2()
                }
            }
        }
    }
}

first.groovy

def test1(){
    //add code for this method
}
def test2(){
    //add code for this method
}
return this

second.groovy

import groovy.transform.Field
@Field private First = null

def init(first) {
    First = first
}
def test1(){
    //add code for this method
}
def test2(){
    First.test2()
}
return this
awefsome
  • 1,431
  • 1
  • 13
  • 11
20

You have to do checkout scm (or some other way of checkouting code from SCM) before doing load.

Krzysztof Krasoń
  • 26,515
  • 16
  • 89
  • 115
  • 3
    This assumes the file to load is in SCM. – Roy Tinker Sep 30 '16 at 18:45
  • You are correct that if the file is in SCM, then you need to get it from there before trying to load it. But, if the library file is in the same repo as the main jenkinsfile, then you don't have to call checkout if the pipeline is configured to pull the repo automatically; in the job config. – steve Feb 28 '21 at 13:39
10

Thanks @anton and @Krzysztof Krasori, It worked fine if I combined checkout scm and exact source file

Example.Groovy

def exampleMethod() {
    println("exampleMethod")
}

def otherExampleMethod() {
    println("otherExampleMethod")
}
return this

JenkinsFile

node {
    // Git checkout before load source the file
    checkout scm

    // To know files are checked out or not
    sh '''
        ls -lhrt
    '''

    def rootDir = pwd()
    println("Current Directory: " + rootDir)

    // point to exact source file
    def example = load "${rootDir}/Example.Groovy"

    example.exampleMethod()
    example.otherExampleMethod()
}
Mallikarjunarao Kosuri
  • 1,023
  • 7
  • 25
  • 52
  • This was what I needed to allow a Jenkins pipeline from SCM to work, now I have all my constants and functions centralized into a common Jenkinsfile.common.Groovy that is shared between my release pipeline and my integration test pipeline. – ChrisPrime Nov 20 '18 at 01:51
  • https://jenkins.io/doc/pipeline/steps/workflow-cps/#code-load-code-evaluate-a-groovy-source-file-into-the-pipeline-script – Saikat Feb 18 '19 at 08:39
8

In case the methods called on your loaded groovy script come with their own node blocks, you should not call those methods from within the node block loading the script. Otherwise you'd be blocking the outer node for no reason.

So, building on @Shishkin's answer, that could look like

Example.Groovy

def exampleMethod() {
    node {
        //do something
    }
}

def otherExampleMethod() {
    node {
        //do something else
    }
}
return this

Jenkinsfile

def exampleModule
node {
    checkout scm // could not get it running w/o checkout scm
    exampleModule = load "script/Example.Groovy"
}
exampleModule.exampleMethod()
exampleModule.otherExampleMethod()

Jenkinsfile using readTrusted

When running a recent Jenkins, you will be able to use readTrusted to read a file from the scm containing the Jenkinsfile without running a checkout - or a node block:

def exampleModule = evaluate readTrusted("script/Example.Groovy")
exampleModule.exampleMethod()
exampleModule.otherExampleMethod()
Joerg S
  • 4,730
  • 3
  • 24
  • 43
  • This works perfectly, even with more complex pipeline (ie. with try/catch, stages, parallel node). Thanks for the tip! – fuujuhi Oct 14 '22 at 08:55
  • My question is how do you create these additional groovy files using jenkins and saving them somewhere where it's accessible by the build? that's what im confused about – mike01010 May 17 '23 at 16:09
  • how does 'scm checkout' know what repo to pull from or what credentials to use? – mike01010 May 17 '23 at 21:26
  • Please check the Jenkins documentation for `checkout scm`: It just checks out whatever repository the Jenkinsfile was read from. Therefore, it will obviuosly not work when writing the Jenkinsfile in the Jenkins web UI. But anyways that you should not do for productive jobs. – Joerg S May 23 '23 at 09:46
4

Very useful thread, had the same problem, solved following you.

My problem was: Jenkinsfile -> call a first.groovy -> call second.groovy

Here my solution:

Jenkinsfile

node {
  checkout scm
  //other commands if you have

  def runner = load pwd() + '/first.groovy'
  runner.whateverMethod(arg1,arg2)
}

first.groovy

def first.groovy(arg1,arg2){
  //whatever others commands

  def caller = load pwd() + '/second.groovy'
  caller.otherMethod(arg1,arg2)
}

NB: args are optional, add them if you have or leave blank.

Hope this could helps further.

dazito
  • 7,740
  • 15
  • 75
  • 117
kancio
  • 41
  • 3
  • 4
    A quick reminder, load() only works within node(). Second load() works because whateverMethod() is called within node(). – vezun Nov 07 '19 at 21:15