Writing a custom step task with GitUtilities

In this tutorial, you will create and add a custom step task to the Cobol language task. This step has the following purposes:

  • Get additional metadata for the current build file from Git by using the GitUtilities class.
  • Add that metadata to new user-generated context variables.
  • Use those variables to pass that metadata to an IDENTIFY statement in the LinkEdit step.

Prerequisites

Estimated time

This tutorial takes about 25 minutes to complete.

Steps

1. Write the Groovy task

This Groovy task will use the GitUtilities DBB class to get additional metadata about the current build file. It will get the git hash in which the file was last changed and the current git branch. This information will then be added to new context variables GIT_HASH and GIT_BRANCH.

  1. Create an IBM-1047 encoded file called gitUtilsTask.groovy in your $DBB_BUILD/groovy folder.

  2. Use a BaseScript AST transformation to set the base script class to TaskScript:

    @groovy.transform.BaseScript com.ibm.dbb.groovy.TaskScript baseScript
    

    This causes the Groovy script to extend TaskScript, injecting an SLF4j logger under the log variable as well as the BuildContext object context and TaskVariables object config.

  3. Import additional classes. This script uses TaskConstants to access the names of DBB provided BuildContext variables, and GitUtilities to interact with the Git repository:

    import com.ibm.dbb.task.TaskConstants
    import com.ibm.dbb.utils.GitUtilities
    
  4. Use the TaskVariables object config to get the path of the current build file that is being processed:

    File file = new File(config.getStringVariable(TaskConstants.FILE_PATH))
    
  5. Get the full hash of the commit in which the file was most recently updated. To do this, use the getFileCurrentGitHash(File file, boolean abbrev) method of GitUtilities, passing the build file from the previous step and true to get the abbreviated Git hash:

    String hash = GitUtilities.getFileCurrentGitHash(file, true)
    

    Create and set the GIT_HASH variable of TaskVariables, so that its value is accessible in the LanguageTask configuration:

    config.setVariable("GIT_HASH", hash)
    

    Add logging to debug the script in the event of an error:

    log.debug("HASH: {}", config.getStringVariable("GIT_HASH"))
    
  6. Repeat these steps for the current branch:

    String branch = GitUtilities.getCurrentGitBranch(file.getAbsolutePath())
    config.setVariable("GIT_BRANCH", branch)
    log.debug("BRANCH: {}", config.getStringVariable("GIT_BRANCH"))
    
  7. Add a return statement and put it all together:

    @groovy.transform.BaseScript com.ibm.dbb.groovy.TaskScript baseScript
    
    import com.ibm.dbb.task.TaskConstants
    import com.ibm.dbb.utils.GitUtilities
    
    // Get absolute file path of the current build file
    File file = new File(config.getStringVariable(TaskConstants.FILE_PATH))
    
    // Get the full hash of the commit in which the file was most recently updated
    String hash = GitUtilities.getFileCurrentGitHash(file, true)
    config.setVariable("GIT_HASH", hash)
    log.debug("HASH: {}", config.getStringVariable("GIT_HASH"))
    
    // Get the name of the current git branch
    String branch = GitUtilities.getCurrentGitBranch(file.getAbsolutePath())
    config.setVariable("GIT_BRANCH", branch)
    log.debug("BRANCH: {}", config.getStringVariable("GIT_BRANCH"))
    
    return 0
    

2. Implement the step in the Cobol.yaml language task

After writing the Groovy script, it is time to implement it in Cobol.yaml. For each build file, the script will be called, populating the TaskVariables object config with variables to store git metadata. These variables will then be used within the linkEdit step to populate the SYSREF dd with instream metadata.

  1. In Cobol.yaml, define a step to execute the groovy task:

    - step: GitUtils
     type: task
     name: gitUtilsTask
    

    This step must execute before the linkEdit step in which the metadata will be used. Ensure your Groovy script is located in $DBB_BUILD/groovy.

  2. Define the SYSREF dd within the linkEdit step:

    dds:
        - { name: "SYSREF", instream: "${instream}", options: "tracks space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
    

    The instream variable that holds the Git metadata will be defined in the next step.

  3. Define the instream variable within the variables section of Cobol.yaml. The |1 notation is a block scalar in YAML syntax. It defines that line breaks are kept and that the string is to be indented by one space.

    - name: instream
        value: |1
          INCLUDE SYSLIB(${MEMBER})
          IDENTIFY ${MEMBER}('BR:${GIT_BRANCH} SHA:${GIT_HASH}')
          NAME ${MEMBER}(R)
    
  4. Putting it all together, the Cobol.groovy task should now look like this:

     ---
     #
     # Licensed materials - Property of IBM
     # 5655-AC5 Copyright IBM Corp. 2024, 2025.
     # All rights reserved
     # US Government users restricted rights  -  Use, duplication or
     # disclosure restricted by GSA ADP schedule contract with IBM Corp.
     #
     ####################################
     # Cobol language configuration
     #################################### 
    
     version: 1.0.0
     tasks:
     # Cobol Language task
     - language: Cobol
    
         # source file association patterns
         sources:
         - "**/cobol/*.cbl"
         
         # overridable variables
         variables:
         # conditional variable builds compile parameters i.e CICS,SQL
         - name: compileParms
             append:
             - condition: ${IS_CICS}
                 value: CICS
             - condition: ${IS_SQL}
                 value: SQL
             - condition: 
                 exists: errPrefix
                 value: ADATA,EX(ADX(ELAXMGUX))
             - condition: 
                 exists: debug
                 eval: ${debug}
                 value: TEST
                 
         # flag to perform linkedit
         - name: doLinkEdit
             value: true
             
         # default link edit parameters
         - name: linkEditParms
             value: MAP,RENT,COMPAT(PM5)
             
         # default dependency search path for single repository build
         - name: dependencySearchPath
             value: search:${WORKSPACE}/?path=${APP_DIR_NAME}/copybook/*.cpy
    
         # flag incating to scan the load module for static link dependencies  
         - name: scanLoadModule
             value: true
             
         # Uncomment to resolve logical files using resolveSubsystems
         # - name: subsystemSearchPath
         #   value: ${dependencySearchPath}
    
         - name: instream
             value: |1
             INCLUDE SYSLIB(${MEMBER})
             IDENTIFY ${MEMBER}('BR:${GIT_BRANCH} SHA:${GIT_HASH}')
             NAME ${MEMBER}(R)
    
         # datasets that need to be created / validated for this language configuration
         datasets:
         - name: ${HLQ}.COBOL
             options: cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library)
         - name: ${HLQ}.COPY
             options: cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library)
         - name: ${HLQ}.BMS.COPY
             options: cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library)
         - name: ${HLQ}.DBRM
             options: cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library)
         - name: ${HLQ}.OBJ
             options: cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library)
         - name: ${HLQ}.LOAD
             options: cyl space(1,1) dsorg(PO) recfm(U) blksize(32760) dsntype(library)
    
         # list of steps to execute for each program processed by this language configuration
         steps:
         
         # Copy build file and dependency files to data sets
         - step: copySrc
             type: copy
             source: ${WORKSPACE}/${FILE}
             target: //'${HLQ}.COBOL(${MEMBER})'
             dependencyCopy:
             - search: ${dependencySearchPath}
                 mappings:
                 - source: "**/*"
                     dataset: ${HLQ}.COPY
                     
         # COBOL compile step          
         - step: compile
             type: mvs
             pgm: IGYCRCTL
             parm: ${compileParms}
             maxRC: 8
             dds:
             - { name: "SYSIN", dsn: "${HLQ}.COBOL(${MEMBER})", options: "shr", input: true }
             - { name: "SYSLIN", dsn: "${HLQ}.OBJ(${MEMBER})", options: "shr", output: true }
             - { name: "TASKLIB", dsn: "${SIGYCOMP}", options: "shr" }
             - {                  dsn: "${SDFHLOAD}", condition: "${IS_CICS}", options: "shr" }
             - {                 dsn: "${SDSNLOAD}", condition: "${IS_SQL}", options: "shr" }
             - {                 dsn: "${SFELLOAD}", condition: { exists: "SFELLOAD" }, options: "shr" }
             - { name: "SYSPRINT", log: "${LOGS}/${MEMBER}-${STEP}.cbl.log", options: "cyl space(5,5) unit(vio) blksize(133) lrecl(133) recfm(f,b) new" }
             - { name: "SYSMDECK", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT1", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT2", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT3", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT4", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT5", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT6", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT7", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT8", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new"}
             - { name: "SYSUT9", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT10", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT11", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT12", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT13", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT14", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT15", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT16", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSUT17", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSLIB", dsn: "${HLQ}.COPY", options: "shr" }
             - {                 dsn: "${HLQ}.BMS.COPY", options: "shr" }
             - {                 dsn: "${SDFHCOB}", condition: "${IS_CICS}", options: "shr" }
             - {                 dsn: "${SCSQCOBC}", condition: "${IS_MQ}", options: "shr" }
             - { name: "DBRMLIB", dsn: "${HLQ}.DBRM(${MEMBER})", condition: "${IS_SQL}", options: "shr", output: true, deployType: "DBRM" }          
             - { name: "SYSADATA", condition: { exists: "errPrefix" }, options: "DUMMY" }
             - { name: "SYSXMLSD", condition: { exists: "errPrefix" }, dsn: "${HLQ}.${errPrefix}.SYSXMLSD.XML", options: "tracks space(200,40) dsorg(PS) blksize(27998) lrecl(16383) recfm(v,b) new keep" }
    
         # GitUtils Step
         - step: GitUtils
             type: task
             name: gitUtilsTask
    
         # Link-Edit step
         - step: linkEdit
             type: mvs
             pgm: IEWBLINK
             parm: ${linkEditParms}
             condition: ${doLinkEdit}
             maxRC: 0
             dds:
             - { name: "SYSREF", instream: "${instream}", options: "tracks space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "SYSLIN", dsn: "${HLQ}.OBJ(${MEMBER})", options: "shr" }
             - { ref: "SYSREF" }
             - { name: "SYSLMOD", dsn: "${HLQ}.LOAD(${MEMBER})", options: "shr", output: true, deployType: "LOAD", scan: "${scanLoadModule}" }
             - { name: "SYSPRINT", log: "${LOGS}/${MEMBER}-${STEP}.cbl.log", options: "cyl space(5,5) unit(vio) blksize(133) lrecl(133) recfm(f,b) new" }
             - { name: "SYSUT1", options: "cyl space(5,5) unit(vio) blksize(80) lrecl(80) recfm(f,b) new" }
             - { name: "RESLIB", condition: { exists: RESLIB }, options: "shr" }
             - { name: "SYSLIB", dsn: "${HLQ}.OBJ", options: "shr" }
             - {                 dsn: "${SCEELKED}", options: "shr" }
             - {                 dsn: "${SDFHLOAD}", condition: "${IS_CICS}", options: "shr" }
             - {                 dsn: "${SDSNLOAD}", condition: "${IS_SQL}", options: "shr" }
    

Next steps

To build an application by using this custom task, ensure the project application is initialized to a valid Git repository. See Building the MortgageApplication sample to learn more about building an application within a Git repository.

After building the application, you can verify the identified metadata easily by reading the log file generated by SYSPRINT in the LinkEdit step above. For example, the log file for epscmort.cbl would be EPSCMORT-linkEdit.cbl.log and will include the following content on lines 4-6:

 IEW2322I 1220  1    INCLUDE SYSLIB(EPSCMORT)
 IEW2322I 1220  2    IDENTIFY EPSCMORT('BR:main SHA:12345678')
 IEW2322I 1220  3    NAME EPSCMORT(R)