I have a work-around to my own question.
According to the documentation (https://www.jenkins.io/doc/book/pipeline/syntax/#declarative-matrix) what I am looking for is not possible. "Each cell in a matrix can include one or more stages to be run sequentially using the configuration for that cell. Note that a stage must have one and only one of steps, stages, parallel, or matrix. It is not possible to nest a parallel or matrix block within a stage directive if that stage directive is nested within a parallel or matrix block itself."
But.... you can cheat. I was able to turn the matrix into a generic dispatch queue. i.e. each combination invokes N stages - for me, 2 and I call them "prepare" and "execute". I pass an additional "matrixtype" argument in. The matrixtype gets 1 value for the matrix itself, as well as additional values for each line of the non-matrix. I then use the matrix 'excludes' to insure that the non-matrix lines execute only once. Effectively, this folds the non matrix into the matrix differentiated by matrix type.
Original (parallel-stages in series with a subsequent matrix)
stage {
parallel {
stage("alpha") {
alpha(..)
}
stage("beta") {
beta(..)
}
// etc
}
}
stage {
matrix {
axes {
axis {
name 'ORIGAXIS'
values 'FOO','BAR','BAZ'
}
}
stages {
stage("First") {
originalFirst( ...)
}
stage("Second") {
originalSecond(...)
}
}
}
}
Replacement (parallel folded into matrix)
stage {
matrix {
axes {
axis {
name 'MATRIXTYPE
values 'ORIGINAL', 'ALPHA', 'BETA'
axis {
name 'ORIGAXIS'
values 'FOO','BAR','BAZ'
}
excludes {
// Execute Alpha and Beta only once (during 'FOO')
exclude {
axis {
name 'MATRIXTYPE'
values 'ALPHA', 'BETA'
}
axis {
name 'ORIGAXIS'
values 'BAR','BAZ'
}
}
}
}
stages {
stage("First") {
dispatchFirst( "${MATRIXTYPE}", ...)
}
stage("Second") {
dispatchSecond( "${MATRIXTYPE}", ...)
}
}
}
}
The dispatchFirst(..) and dispatchSecond(..) are then simple dispatch methods in the shared lib that examine matrixtype, and invoke originalFirst(..), originalSecond(..), alpha(..), beta(..) or a no-op as appropriate. It's slightly clumsy, and amounts to shoehorning the parallel stages into the matrix, but it works. And, you get the benefit of parallelization (build speed-optimization)
Hopefully in the future, there will be something more elegant.
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
)
}
}
}
}