1@NonCPS
2List generateMatrix(Map matrixAxes) {
3    List axes = []
4
5    matrixAxes.each { axis, values ->
6        List axisList = []
7
8        values.each { value ->
9            axisList << [(axis): value]
10        }
11
12        axes << axisList
13    }
14
15    axes.combinations()*.sum()
16}
17
18pipeline {
19    agent {
20        label 'docker'
21    }
22
23    stages {
24        stage('Build container') {
25            steps {
26                /*
27                 * Using `--target` with the Dockerfile agent currently bugs out
28                 * on Jenkins, producing an error along the lines of:
29                 *
30                 *     Cannot retrieve .Id from 'docker inspect ...'
31                 *
32                 * Apparently it doesn't like multi-stage builds, so until
33                 * we're on a version of the Docker workflow plugin that
34                 * doesn't exhibit this bug, we just have to build the image
35                 * ourselves.
36                 *
37                 * See:
38                 *   - https://github.com/jenkinsci/docker-workflow-plugin/pull/149
39                 *   - https://github.com/jenkinsci/docker-workflow-plugin/pull/162
40                 *   - https://github.com/jenkinsci/docker-workflow-plugin/pull/180
41                 *
42                 * Once the Docker workflow plugin has been updated, we should
43                 * be able to get rid of this stage and use:
44                 *
45                 *     agent {
46                 *         dockerfile {
47                 *             dir 'docker'
48                 *             filename 'Dockerfile'
49                 *             additionalBuildArgs '--target ci'
50                 *         }
51                 *     }
52                 */
53
54                sh """ \
55                    docker build -t scp-firmware:build-${currentBuild.number} \
56                        --build-arg JENKINS_UID=1000 \
57                        --build-arg JENKINS_GID=36293 \
58                        --target=jenkins docker
59                """
60            }
61        }
62
63        stage('Run tests') {
64            parallel {
65                stage('Run legacy tests') {
66                    agent {
67                        docker {
68                            image "scp-firmware:build-${currentBuild.number}"
69                            args '-e ARMLMD_LICENSE_FILE'
70                        }
71                    }
72
73                    steps {
74                        sh '/usr/local/bin/init'
75                        sh 'python3 ./tools/ci.py'
76                    }
77                }
78
79                stage('Build and test') {
80                    /*
81                     * We are on an old enough version of the Jenkins pipeline
82                     * workflow plugin that we do not have support for matrices.
83                     *
84                     * Without this support, we need to generate the stages as
85                     * part of a scripted stage.
86                     *
87                     * See:
88                     *  - https://www.jenkins.io/blog/2019/11/22/welcome-to-the-matrix/
89                     *  - https://www.jenkins.io/blog/2019/12/02/matrix-building-with-scripted-pipeline/
90                     *  - https://stackoverflow.com/questions/60829465/jenkins-unknown-stage-section-matrix-in-declarative-pipeline
91                     *
92                     * Once the plugin has been updated, we can adopt a proper
93                     * matrix.
94                     */
95
96                    steps {
97                        script {
98                            /*
99                             * This is, admittedly, not particularly clean, but
100                             * should require very little adjustment. The
101                             * premise is relatively simple: generate a list of
102                             * all the possible matrix combinations, create a
103                             * a list of closures returning a pipeline stage for
104                             * each combination, then execute them in parallel.
105                             */
106
107                            def axes = [
108                                generator: [ 'Ninja', 'Unix Makefiles' ],
109                                buildType: [ 'Debug', 'Release', 'MinSizeRel',
110                                             'RelWithDebInfo' ]
111                            ]
112
113                            def tasks = generateMatrix(axes).collectEntries { config ->
114                                ["Build and test (${config})", {
115                                    node('docker') {
116                                        docker
117                                            .image("scp-firmware:build-${currentBuild.number}")
118                                            .inside('-e ARMLMD_LICENSE_FILE')
119                                        {
120                                            /*
121                                             * If you need to adjust the
122                                             * behaviour of the generated stages
123                                             * then this is probably where you
124                                             * want to do it.
125                                             */
126
127                                            stage("Build ${config}") {
128                                                checkout scm
129
130                                                /*
131                                                 * Unfortunately, we don't have
132                                                 * the CMake build plugin
133                                                 * available to us, so we'll
134                                                 * have to make do with shell
135                                                 * scripts for now.
136                                                 */
137
138                                                sh '/usr/local/bin/init'
139
140                                                sh """ \
141                                                    cmake \
142                                                        -G "${config.generator}" \
143                                                        -DCMAKE_BUILD_TYPE="${config.buildType}" \
144                                                        .
145                                                """
146
147                                                sh 'cmake --build .'
148                                            }
149
150                                            stage("Check ${config}") {
151                                                sh """
152                                                    cmake
153                                                        --build . \
154                                                        --target check
155                                                """
156                                            }
157                                        }
158                                    }
159                                }]
160                            }
161
162                            parallel tasks
163                        }
164                    }
165                }
166            }
167        }
168    }
169}
170