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

Job DSL: strange behavior from "configure"

    Details

    • Similar Issues:

      Description

      I am using the "configure" command to directly modify the XML since the Template Plugin is not supported directly.

      This is simple groovy code that produces (in my opinion) a false ArrayIndexOutOfBoundsException:

      job("Template Output Job") {
        String[] templates = ["Template1", "Template2"]
        for (int i = 0; i < templates.length; i++) {
          configure {
            it / "builders" << "hudson.plugins.templateproject.ProxyBuilder" {
              "projectName"(templates[i]);
            }
          }
        }
      }
      

      I can easily fix this by using a temporary variable:

      job("Template Output Job") {
        String[] templates = ["Template1", "Template2"]
        for (int i = 0; i < templates.length; i++) {
          String template = templates[i];
          configure {
            it / "builders" << "hudson.plugins.templateproject.ProxyBuilder" {
              "projectName"(template);
            }
          }
        }
      }
      

      There may be a good reason for this, but it is very strange from an end user point of view...

        Attachments

          Activity

          Hide
          daspilker Daniel Spilker added a comment -

          This is actually a feature. The configure block takes a closure argument (the block with the curly braces). That block is not evaluated immediately but when the config XML generated after the script has been processed.

          When a Groovy closure is defined, it captures the variables but not their values. In your case it captures it i variable. And since the value of i is 2 after the loop (when the closure is actually called), you get an ArrayIndexOutOfBoundsException.

          You can test this behavior by running this script in the Jenkins Script Console:

          def a = 1
          
          def c = {
            println a
          }
          
          c()
          
          a = 2
          
          c()
          
          a = 3
          
          c()
          

          The output is

          1
          2
          3
          

          To avoid this problem you could iterate through the array using each (which is also Groovyier):

          job('Template Output Job') {
            String[] templates = ['Template1', 'Template2']
            templates.each { String template ->
              configure {
                it / builders << 'hudson.plugins.templateproject.ProxyBuilder' {
                  projectName(template)
                }
              }
            }
          }
          
          Show
          daspilker Daniel Spilker added a comment - This is actually a feature. The configure block takes a closure argument (the block with the curly braces). That block is not evaluated immediately but when the config XML generated after the script has been processed. When a Groovy closure is defined, it captures the variables but not their values. In your case it captures it i variable. And since the value of i is 2 after the loop (when the closure is actually called), you get an ArrayIndexOutOfBoundsException . You can test this behavior by running this script in the Jenkins Script Console: def a = 1 def c = { println a } c() a = 2 c() a = 3 c() The output is 1 2 3 To avoid this problem you could iterate through the array using each (which is also Groovyier): job( 'Template Output Job' ) { String [] templates = [ 'Template1' , 'Template2' ] templates.each { String template -> configure { it / builders << 'hudson.plugins.templateproject.ProxyBuilder' { projectName(template) } } } }
          Hide
          w60001 Christopher Shannon added a comment -

          I appreciate the explanation, and can certainly update to use an iterator instead.

          The only thing I don't quite get is why my second code example works correctly. Based on your explanation, when the XML is finally generated, wouldn't I expect to see my second array element used twice? If both configure calls only capture the "template" variable but not the value, then shouldn't my XML have two calls to "Template 2"?

          This was changed from a bug report to a Groovy lesson...

          Show
          w60001 Christopher Shannon added a comment - I appreciate the explanation, and can certainly update to use an iterator instead. The only thing I don't quite get is why my second code example works correctly. Based on your explanation, when the XML is finally generated, wouldn't I expect to see my second array element used twice? If both configure calls only capture the "template" variable but not the value, then shouldn't my XML have two calls to "Template 2"? This was changed from a bug report to a Groovy lesson...
          Hide
          daspilker Daniel Spilker added a comment -

          The second example works because the template variable is defined in the loop. So it's a new variable in every iteration that does not change it's value. If you define the variable outside of the loop, you would see the last element twice:

          job("Template Output Job") {
            String[] templates = ["Template1", "Template2"]
            String template
            for (int i = 0; i < templates.length; i++) {      
              template = templates[i];
              configure {
                it / "builders" << "hudson.plugins.templateproject.ProxyBuilder" {
                  "projectName"(template);
                }
              }
            }
          }
          
          Show
          daspilker Daniel Spilker added a comment - The second example works because the template variable is defined in the loop. So it's a new variable in every iteration that does not change it's value. If you define the variable outside of the loop, you would see the last element twice: job( "Template Output Job" ) { String [] templates = [ "Template1" , "Template2" ] String template for ( int i = 0; i < templates.length; i++) { template = templates[i]; configure { it / "builders" << "hudson.plugins.templateproject.ProxyBuilder" { "projectName" (template); } } } }
          Hide
          w60001 Christopher Shannon added a comment -

          Ah, makes perfect sense. Thanks for the explanation!

          Show
          w60001 Christopher Shannon added a comment - Ah, makes perfect sense. Thanks for the explanation!

            People

            • Assignee:
              daspilker Daniel Spilker
              Reporter:
              w60001 Christopher Shannon
            • Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: