Search code examples
jenkinscmakejenkins-pipelinegoogletestjenkins-declarative-pipeline

How to use Jenkins declarative pipeline to build and test on multiple platforms


I'm trying to do something that I feel should be simple to do, but I can't figure out how.

Basically I have a Jenkins master (running on Linux) and two slaves, one on Windows and the other on macOS.

I want to build my project on all 3 platforms and run GTest tests on all 3 platforms too.

I can build and run the test, but the junit step doesn't seem to collect any test results.

I tried to put the post block everywhere, but it just doesn't work. If I try to put the post block in the Test stage or as a sibling of stages, I get the following error:

Required context class hudson.FilePath is missing Perhaps you forgot to surround the code with a step that provides this, such as: node which is caused by agent none - the post block doesn't know where to run.

So I tried to put the post block inside the node block in my parallel step for the Test stage, but it doesn't seem to do anything - it doesn't even show up in the console output.

Here's my Jenkinsfile:

pipeline {
    agent none
    stages {
        stage ('Clean') {
            steps {
                parallel (
                    "linux" : {
                        node ("linux") {
                            dir("build") {
                                deleteDir()
                                writeFile file:'dummy', text:'' // Creates the directory
                            }
                        }
                    },
                    "windows" : {
                        node('windows') {
                            dir("build") {
                                deleteDir()
                                writeFile file:'dummy', text:'' // Creates the directory
                            }
                        }
                    },
                    "mac" : {
                        node('mac') {
                            dir("build") {
                                deleteDir()
                                writeFile file:'dummy', text:''  // Creates the directory
                            }
                        }
                    }
                )
            }
        }

        stage ('Build') {
            steps {
                parallel (
                    "linux" : {
                        node ("linux") {
                            checkout scm
                            dir("build") {
                                sh '/opt/cmake/bin/cmake .. -DCMAKE_BUILD_TYPE=Release'
                                sh 'make'
                            }
                        }
                    },
                    "windows" : {
                        node('windows') {
                            checkout(changelog: false, scm: scm) // Changelog to false, otherwise Jenkins shows duplicates. Only linux (the Jenkins master) has the changelog enabled.
                            dir("build") {
                                bat 'cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_PREFIX_PATH=C:/Qt/5.9.1/msvc2017_64'
                                bat "\"${tool 'MSBuild'}\" project.sln /p:Configuration=Release /p:Platform=\"x64\" /p:ProductVersion=1.0.0.${env.BUILD_NUMBER} /m"
                            }
                        }
                    },
                    "mac" : {
                        node('mac') {
                            checkout(changelog: false, scm: scm) // Changelog to false, otherwise Jenkins shows duplicates. Only linux (the Jenkins master) has the changelog enabled.
                            dir("build") {
                                sh 'cmake .. -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1 -DCMAKE_BUILD_TYPE=Release'
                                sh 'make'
                            }
                        }
                    }
                )
            }
        }

        stage ('Test') {
            steps {
                parallel (
                    "linux" : {
                        node ("linux") {
                            dir('Build') {
                                sh './bin/project-tests --gtest_output=xml:project-tests-results.xml'
                                // Add other test executables here.
                            }

                            post {
                                always {
                                    junit '*-tests-results.xml'
                                }
                            }
                        }
                    },
                    "windows" : {
                        node('windows') {
                            dir("build") {
                                bat 'tests\\project\\Release\\project-tests --gtest_output=xml:project-tests-results.xml'
                                // Add other test executables here.
                            }

                            post {
                                always {
                                    junit '*-tests-results.xml'
                                }
                            }
                        }
                    },
                    "mac" : {
                        node('mac') {
                            dir("build") {
                                sh './bin/project-tests --gtest_output=xml:project-tests-results.xml'
                                // Add other test executables here.
                            }
                            post {
                                always {
                                    junit '*-tests-results.xml'
                                }
                            }
                        }
                    }
                )
            }
        }
    }

}

What am I doing wrong?


Solution

    1. post{} block should only follow steps{} or parallel{} (for parallel stages) to take effect.

    2. If you require post to be executed in a node environment, you should provide a node to the entire stage (agent{} statement).

    You could try to use parallel stages execution. Also I'd suggest to use functions to shorten the code.

    Something like this:

    void Clean() {
        dir("build") {
            deleteDir()
            writeFile file:'dummy', text:'' // Creates the directory
        }
    }
    
    void SmthElse(def optionalParams) {
        // some actions here
    }
    
    pipeline {
        agent none
        options {
            skipDefaultCheckout(true)   // to avoid force checkouts on every node in a first stage
            disableConcurrentBuilds()   // to avoid concurrent builds on same nodes
        }
        stages {
            stage('Clean') {
                failfast false
                parallel {
                        stage('Linux') {
                            agent {label 'linux'}
                            steps {Clean()}
                            post {
                                // post statements for 'linux' node
                                SmthElse(someParameter)
                            }
                        }
                        stage('Windows') {
                            agent {label 'windows'}
                            steps {Clean()}
                            post {
                                // post statements for 'windows' node
                            }
                        }
                        stage('MacOS') {
                            agent {label 'mac'}
                            steps {Clean()}
                            post {
                                // post statements for 'mac' node
                            }
                        }
                }
                post {
                    // Post statements OUTSIDE of nodes (i.e. send e-mail of a stage completion)
                }
            }
    
            // other stages (Build/Test/Etc.)
        }
    }
    

    Alternatively you can use node in post statements:

    stage('Test') {
        steps {
            // your parallel Test steps
        }
        post {
            always {
                script {
                    parallel (
                        "linux" : {
                            node('linux') {
                                // 'linux' node post steps
                            }
                        },
                        "windows" : {
                            node('windows') {
                                // 'windows' node post steps
                            }
                        }
    
                        // etc
                    )
                }
            }
        }
    }