Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-41187

Stage "when" should have "stage" condition

    XMLWordPrintable

Details

    Description

      I'd like to create two stages, where if one runs the other doesn't - effectively and `if-else`.

      Right now I'd have to do something like this:

      stages {
          stage ('Full Build') {
              when {
                  expression { return params.FORCE_FULL_BUILD }
              }
          }
      
          stage ('Incremental Build') {
              when {
                  expression { return !params.FORCE_FULL_BUILD }
              }
          }
      }
      

      For simple expressions, that's no problem. For complex expressions it gets tiresome.

      The `when {}` block needs a way to indicate `else`. This could be done (when combined with a logical `not` condition) by have a `stage` condition. Like so:

      stages {
          stage ('Full Build') {
              when {
                  expression { return params.FORCE_FULL_BUILD }
              }
          }
      
          stage ('Incremental Build') {
              when {
                  not { stage 'Full Build' }
              }
          }
      }
      

      This would also allow for stages to depend on previous stages in a clear fashion. For example:

      stages {
          stage ('Full Build') {
              when {
                  expression { return params.FORCE_FULL_BUILD }
              }
          }
      
          stage ('Incremental Build') {
              when {
                  not { stage 'Full Build' }
              }
          }
      
          stage ('Full Tests') {
              when {
                  stage 'Full Build' }
              }
          }
      
          stage ('Incremental Tests') {
              when {
                  stage 'Incremental Build' }
              }
          }
      }
      

      Attachments

        Issue Links

          Activity

            bitwiseman Liam Newman created issue -
            bitwiseman Liam Newman made changes -
            Field Original Value New Value
            Link This issue relates to JENKINS-41185 [ JENKINS-41185 ]
            hrmpw Patrick Wolf added a comment - - edited

            I don't like this syntax formatting.

            When I see this :

            stage ('Incremental Build') {
                    when {
                        not { stage 'Full Build' }
                    }
                }
            

            I read it as when this Stage is not "Full Build". My first reaction is that is always true.

            If we want to say it is dependent in someway then we need to make that relationship explicit. When stage "Full Build" is not what? Not skipped, not run, not stable? That isn't clear.

            Right now the use case described is easily done as shown. To me that is much more clear than the alternative. We can explore having some stage conditions but they need to be clear.

            Maybe something like:

            stage ('Incremental Build') {
                    when {
                         stage 'Full Build'  == SKIPPED
                    }
                }
            

            Or

            stage ('Incremental Build') {
                    when {
                         stage 'Full Build'  == SUCCESS
                    }
                }
            

            Or

            stage ('Incremental Build') {
                    when {
                         stage 'Full Build'  != UNSTABLE
                    }
                }
            
            hrmpw Patrick Wolf added a comment - - edited I don't like this syntax formatting. When I see this : stage ( 'Incremental Build' ) { when { not { stage 'Full Build' } } } I read it as when this Stage is not "Full Build". My first reaction is that is always true. If we want to say it is dependent in someway then we need to make that relationship explicit. When stage "Full Build" is not what? Not skipped, not run, not stable? That isn't clear. Right now the use case described is easily done as shown. To me that is much more clear than the alternative. We can explore having some stage conditions but they need to be clear. Maybe something like: stage ( 'Incremental Build' ) { when { stage 'Full Build' == SKIPPED } } Or stage ( 'Incremental Build' ) { when { stage 'Full Build' == SUCCESS } } Or stage ( 'Incremental Build' ) { when { stage 'Full Build' != UNSTABLE } }
            bitwiseman Liam Newman added a comment -

            hrmpw
            I'm open to discussion of how it should look, but I think we need some form of this.

            "Right now the use case described is easily done as shown."
            Yes, like I said, for simple expressions things are fine, but for complex expressions this would get unwieldy very fast.

            stages {
                stage ('Full Build') {
                    when {
                        expression {
                            GIT_BRANCH = 'origin/' + sh(returnStdout: true, script: 'git rev-parse --abbrev-ref HEAD').trim()
                            return GIT_BRANCH == 'origin/master' || params.FORCE_FULL_BUILD
                        }
                    }
                }
            
                stage ('Incremental Build') {
                    when {
                        expression {
                            GIT_BRANCH = 'origin/' + sh(returnStdout: true, script: 'git rev-parse --abbrev-ref HEAD').trim()
                            return !(GIT_BRANCH == 'origin/master' || params.FORCE_FULL_BUILD)
                        }
                    }
                }
            }
            

            Then repeat that condition over and over for other stages. Mediocre.

            I see your point about the specific `stage {}` condition syntax I described being not great, but I also don't want to use general comparison operators if we can avoid them here. Also, stages run in serial and failure in a previous stage would generally stop the pipeline right?

            Maybe this:

            stages {
                stage ('Full Build') {
                    when {
                        expression { return params.FORCE_FULL_BUILD }
                    }
                }
            
                stage ('Incremental Build') {
                    when {
                        stages('Full Build').skipped
                    }
                }
            
                stage ('Full Tests') {
                    when {
                        stages('Full Build').executed
                    }
                }
            
                stage ('Incremental Tests') {
                    when {
                        stages('Incremental Build').executed
                    }
                }
            }
            

            Or we could follow your status example:

            stages {
                stage ('Full Build') {
                    when {
                        expression { return params.FORCE_FULL_BUILD }
                    }
                }
            
                stage ('Incremental Build') {
                    when {
                        stages('Full Build').skipped
                    }
                }
            
                stage ('Full Tests') {
                    when {
                        stages('Full Build').succeeded 
                    }
                }
            
                stage ('Incremental Tests') {
                    when {
                        stages('Incremental Build').succeeded
                    }
                }
            }
            
            bitwiseman Liam Newman added a comment - hrmpw I'm open to discussion of how it should look, but I think we need some form of this. "Right now the use case described is easily done as shown." Yes, like I said, for simple expressions things are fine, but for complex expressions this would get unwieldy very fast. stages { stage ( 'Full Build' ) { when { expression { GIT_BRANCH = 'origin/' + sh(returnStdout: true , script: 'git rev-parse --abbrev-ref HEAD' ).trim() return GIT_BRANCH == 'origin/master' || params.FORCE_FULL_BUILD } } } stage ( 'Incremental Build' ) { when { expression { GIT_BRANCH = 'origin/' + sh(returnStdout: true , script: 'git rev-parse --abbrev-ref HEAD' ).trim() return !(GIT_BRANCH == 'origin/master' || params.FORCE_FULL_BUILD) } } } } Then repeat that condition over and over for other stages. Mediocre. I see your point about the specific `stage {}` condition syntax I described being not great, but I also don't want to use general comparison operators if we can avoid them here. Also, stages run in serial and failure in a previous stage would generally stop the pipeline right? Maybe this: stages { stage ( 'Full Build' ) { when { expression { return params.FORCE_FULL_BUILD } } } stage ( 'Incremental Build' ) { when { stages( 'Full Build' ).skipped } } stage ( 'Full Tests' ) { when { stages( 'Full Build' ).executed } } stage ( 'Incremental Tests' ) { when { stages( 'Incremental Build' ).executed } } } Or we could follow your status example: stages { stage ( 'Full Build' ) { when { expression { return params.FORCE_FULL_BUILD } } } stage ( 'Incremental Build' ) { when { stages( 'Full Build' ).skipped } } stage ( 'Full Tests' ) { when { stages( 'Full Build' ).succeeded } } stage ( 'Incremental Tests' ) { when { stages( 'Incremental Build' ).succeeded } } }
            jamesdumay James Dumay made changes -
            Summary [Declarative Pipeline] Stage "when" should have "stage" condition Stage "when" should have "stage" condition
            jamesdumay James Dumay made changes -
            Epic Link JENKINS-45426 [ 183594 ]
            jdcskillet Joe Cavanaugh added a comment -

            I agree with this wholeheartedly. Has this gotten any traction lately? 

            jdcskillet Joe Cavanaugh added a comment - I agree with this wholeheartedly. Has this gotten any traction lately? 
            abayer Andrew Bayer made changes -
            Status Open [ 1 ] In Progress [ 3 ]
            abayer Andrew Bayer added a comment - I'm implementing this in a new plugin - https://github.com/abayer/declarative-pipeline-when-conditions-plugin .
            abayer Andrew Bayer made changes -
            Remote Link This issue links to "new plugin repo (initial) (Web Link)" [ 17831 ]
            abayer Andrew Bayer added a comment - - edited

            Oh, and the conditional syntax is stageHasRun "some-stage" - just stage caused things to blow up at parse-time due to stage being overloaded twice already. =) I went with the original syntax - it checks if the stage has run or is running and hasn't been skipped (due to conditional or earlier failure or whatever). Since stages don't yet have their own statuses in a real way, we don't have any way to compare against stage status.

            abayer Andrew Bayer added a comment - - edited Oh, and the conditional syntax is stageHasRun "some-stage" - just stage caused things to blow up at parse-time due to stage being overloaded twice already. =) I went with the original syntax - it checks if the stage has run or is running and hasn't been skipped (due to conditional or earlier failure or whatever). Since stages don't yet have their own statuses in a real way, we don't have any way to compare against stage status.
            bnacey Brian Nacey added a comment - - edited

            Has there been any progress on this? I find myself in need of a solution to know if an earlier stage has been built.

            bnacey Brian Nacey added a comment - - edited Has there been any progress on this? I find myself in need of a solution to know if an earlier stage has been built.
            rittneje Jesse Rittner added a comment -

            I found myself needing something similar - a set of mutually exclusive stages of which only one should execute. I would actually prefer something like this:

             

             

            stage ("Do It") {
                switch {
                    stage("Stage A") {
                        when { expression { return conditionA } }
                        steps { ... }
                    }
                    stage("Stage B") {
                         when { expression { return conditionB } }
                         steps { ... }
                    }
                    stage("Stage C") {
                        steps { ... }
                    }
                }
            }
            

            This means "If condition A, do Stage A. Else if condition B, do Stage B. Else, do Stage C." switch would be at the same level as stages, parallel, and matrix today. This allows Blue Ocean to clearly display the mutually exclusive stages (probably in a vertical arrangement, like it does for parallel).

             

             

            rittneje Jesse Rittner added a comment - I found myself needing something similar - a set of mutually exclusive stages of which only one should execute. I would actually prefer something like this:     stage ( "Do It" ) { switch { stage( "Stage A" ) { when { expression { return conditionA } } steps { ... } } stage( "Stage B" ) { when { expression { return conditionB } } steps { ... } } stage( "Stage C" ) { steps { ... } } } } This means "If condition A, do Stage A. Else if condition B, do Stage B. Else, do Stage C." switch would be at the same level as stages , parallel , and matrix today. This allows Blue Ocean to clearly display the mutually exclusive stages (probably in a vertical arrangement, like it does for parallel ).    
            bitwiseman Liam Newman added a comment -

            rittneje
            Wow. Yeah, I like that idea. It is a separate idea from this, though I see the relation. Please file an issue for it.

            bnacey
            No there hasn't been more progress on this issue. It stalled out. Restarting it would require some effort.

            bitwiseman Liam Newman added a comment - rittneje Wow. Yeah, I like that idea. It is a separate idea from this, though I see the relation. Please file an issue for it. bnacey No there hasn't been more progress on this issue. It stalled out. Restarting it would require some effort.
            abayer Andrew Bayer made changes -
            Assignee Andrew Bayer [ abayer ]

            People

              Unassigned Unassigned
              bitwiseman Liam Newman
              Votes:
              11 Vote for this issue
              Watchers:
              16 Start watching this issue

              Dates

                Created:
                Updated: