Tutorial: Writing your first build script

The DBB API is written in Java and can be called by Java applications as well as Java-based scripting languages, such as Groovy, JRuby, Jython, Ant, and Maven. DBB includes an installation of Apache® Groovy that has been modified to run on z/OS UNIX. All of the build scripting examples in this tutorial use Groovy as the scripting language.

You can follow this tutorial to write a build script by using the DBB APIs to compile a simple "Hello World" COBOL program.

Prerequisites: Creating a Hello World COBOL program

While DBB does not dictate the type or location of the SCM used to store your source files, a common use case is that they are stored on a distributed SCM such as Git. At the beginning of the build, the latest version of the source files is pulled to a local directory on zFS. The build script example below uses /u/usr1/build as its working directory.

Create a file called helloworld.cbl and add the following content.

       IDENTIFICATION DIVISION.
       PROGRAM-ID.     HELLO.
   
       PROCEDURE DIVISION.
           DISPLAY "Hello world!".
           STOP RUN.

Creating a Hello World Groovy build script

In this tutorial, we create the hello world build script in the same directory as the hello world COBOL program; however, it is not necessary that they be in the same directory. Create a file called helloworld.groovy and open it with an editor. The procedure to create the Groovy build script contains the following steps:

  1. Add package import statements
  2. Create data sets in PDS
  3. Copy the source file from the zFS directory to a PDS
  4. Compile the program
  5. Copy the SYSPRINT to zFS
  6. Finish the build script

The detailed step instructions are as follows:

1. Add package import statements

Groovy is a JVM scripting language and it shares many syntactic elements with Java. Add package import statements at the beginning of the script. The DBB API consists of classes located in packages that begin with com.ibm.dbb. In this tutorial, we need to import just the DBB build package.

import com.ibm.dbb.build.*

2. Create data sets in PDS

For your convenience, DBB provides the CreatePDS command to be used in your build script to create any needed data sets. The command is safe to reuse in that it will create the PDS only if it does not exist.

new CreatePDS().dataset("USR1.BUILD.COBOL").options("cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library) msg(1)").execute()
new CreatePDS().dataset("USR1.BUILD.OBJ").options("cyl space(1,1) lrecl(80) dsorg(PO) recfm(F,B) dsntype(library) msg(1)").execute()

3. Copy the source file from the zFS directory to a PDS

The COBOL compiler that we use to compile our hello world COBOL program requires the source file to reside in a PDS for compilation. Therefore, we copy the source file from zFS to PDS first by using the CopyToPDS command class provided by DBB.

def file = new File("/u/usr1/build/helloworld.cbl")
def copy = new CopyToPDS()
copy.setFile(file)
copy.setDataset("USR1.BUILD.COBOL")
copy.setMember("HELLO")
copy.execute()

The CopyToPDS command takes a few arguments: a Java file instance of the source zFS file, the destination PDS, and PDS member name. To run the command, use the execute() method.

Some of the command classes provided by the DBB API might require many arguments to be set before the command can be run. As such, many of the commands have been designed to allow setter method chaining, which is of concise coding style and better readability. Therefore, the same CopyToPDS command defined above can be defined and executed as follows.

def copy = new CopyToPDS().file(new File("/u/usr1/build/helloworld.cbl")).dataset("USR1.BUILD.COBOL").member("HELLO")
copy.execute()

Below is another example.

new CopyToPDS().file(new File("/u/usr1/build/helloworld.cbl")).dataset("USR1.BUILD.COBOL").member("HELLO").execute()

4. Compile the program

Next, we need to define and execute a COBOL compile command. DBB provides the MVSExec command class to execute z/OS programs. Declare a compile command and set the program name and the parameters as follows:

def compile = new MVSExec()
compile.setPgm("IGYCRCTL")
compile.setParm("LIB")

You may notice that the MVSExec command is similar to the JCL EXEC statement. Like JCL, the next step in defining the compile command is to add DD (data definition) statements. Let's create and add the SYSIN DD statement.

def sysin = new DDStatement()
sysin.setName("SYSIN")
sysin.setDsn("USR1.BUILD.COBOL(HELLO)")
sysin.setOptions("shr")
compile.addDDStatement(sysin)

One main difference between a JCL DD statement and a DBB DD statement is that DBB uses BPXWDYN dynamic allocation to allocate and free DD statements during the execution of the build script. This means that the DD statement parameters used in the setOptions() method will be BPXWDYN option equivalents to the JCL DD statement parameters that you may be used to.

For more information on BPXWDYN and the options used to dynamically allocate DD Statements, see BPXWDYN: a text interface to dynamic allocation and dynamic output.

As with the CopyToPDS class, both the MVSExec class and the DDStatement class support setter method chaining. This is useful when you add a number of DD Statements to an MVSExec command before executing. You can rewrite for the compile command by using method chaining and finish adding the remaining DD statements required by the COBOL V6.1 compiler.

def compile = new MVSExec().pgm("IGYCRCTL").parm("LIB")
compile.dd(new DDStatement().name("SYSIN").dsn("USR1.BUILD.COBOL(HELLO)").options("shr"))
compile.dd(new DDStatement().name("SYSLIN").dsn("USR1.BUILD.OBJ(HELLO)").options("shr"))
compile.dd(new DDStatement().name("SYSUT1").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT2").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT3").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT4").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT5").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT6").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT7").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT8").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT9").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT10").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT11").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT12").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT13").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT14").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT15").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT16").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT17").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSMDECK").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("TASKLIB").dsn("IGY.V6R1M0.SIGYCOMP").options("shr"))

Two things to note when adding DD statements to an MVSExec command:

  1. You can define a temporary data set by omitting .dsn(), the DD Statement data set name setter. In the example above, all of the SYSUT* DD statements are defined as temporary data sets.
  2. You can also define DD concatenations. To define a DD concatenation in an MVSExec command, omit .name(), the DD Statement name setter. This causes the DD statement to be concatenated to the last DD statement that was added that has a DD name.

IMPORTANT: A major difference between the JCL EXEC statement and the DBB MVSExec command is that the JCL STEPLIB has been replaced with a TASKLIB in DBB. This is because DBB runs as a Java application in a z/OS UNIX subsystem where the STEPLIB is defined as an environment variable that cannot be modified during the DBB build process. Additionally, DBB invokes z/OS programs using the LINKX and ATTACHX assembler macros. The ATTACHX macro uses the TASKLIB as its search path to locate the program being invoked.

5. Copy the SYSPRINT to zFS

It is often desirable to copy the SYSPRINT outputted from the COBOL compiler to a zFS directory as a log file so that you can view it without having to change environments. DBB provides the CopyToHFS command to copy data sets and data set members to zFS. The CopyToHFS command can even copy temporary data sets during the MVSExec command execution, thus avoiding the necessity of first writing the SYSPRINT to a permanent PDS. You can do this by defining CopyToHFS commands that use the DD name as the source and add them as sub commands to the MVSExec command. The copy commands are executed after the z/OS program is invoked but before the DD statement allocations are freed.

compile.dd(new DDStatement().name("SYSPRINT").options("cyl space(5,5) unit(vio) new"))
compile.copy(new CopyToHFS().ddName("SYSPRINT").file(new File("/u/usr1/build/helloworld.log")))

Now you can add the statement to execute the compile command. The MVSExec execute() method returns the standard z/OS program RC as an int field, so let's capture that in a variable as well. And finally, let's add some code to test the RC and print an appropriate message to the output console.

def rc = compile.execute()

if (rc > 4)
    println("Compile failed!  RC=$rc")
else
    println("Compile successful!  RC=$rc")

6. Finish the build script

Here is the complete build script with a few additional output console print statements to build show status.

import com.ibm.dbb.build.*

println("Copying source from zFS to PDS . . .")
def copy = new CopyToPDS().file(new File("/u/usr1/build/helloworld.cbl")).dataset("USR1.BUILD.COBOL").member("HELLO")
copy.execute()

println("Compiling . . .")
def compile = new MVSExec().pgm("IGYCRCTL").parm("LIB")
compile.dd(new DDStatement().name("SYSIN").dsn("USR1.BUILD.COBOL(HELLO)").options("shr"))
compile.dd(new DDStatement().name("SYSLIN").dsn("USR1.BUILD.OBJ(HELLO)").options("shr"))
compile.dd(new DDStatement().name("SYSUT1").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT2").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT3").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT4").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT5").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT6").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT7").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT8").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT9").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT10").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT11").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT12").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT13").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT14").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT15").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT16").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSUT17").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("SYSMDECK").options("cyl space(5,5) unit(vio) new"))
compile.dd(new DDStatement().name("TASKLIB").dsn("IGY.V6R1M0.SIGYCOMP").options("shr"))
compile.dd(new DDStatement().name("SYSPRINT").options("cyl space(5,5) unit(vio)  new"))
compile.copy(new CopyToHFS().ddName("SYSPRINT").file(new File("/u/usr1/build/helloworld.log")))
def rc = compile.execute()

if (rc > 4)
    println("Compile failed!  RC=$rc")
else
    println("Compile successful!  RC=$rc")

In addition to the helloworld.cbl and the helloworld.groovy files being identified with their correct locations in the build script, the three data sets referenced in the compile MVSExec command definition need to exist for the build script to run successfully.

Running the build script

The DBB toolkit includes an Apache Groovy installation that has been modified to run on z/OS UNIX.

There are two ways to run the build script:

  • To run a Groovy script that contains DBB APIs by using the Groovy installation command directly, add both class path and library path parameters pointing to the DBB toolkit lib directory. Assuming that the DBB toolkit is installed at /usr/lpp/IBM/dbb, the command to run the helloworld.groovy build script is as follows.
/usr/lpp/IBM/dbb/groovy-2.4.12/bin/groovy -cp "/usr/lpp/IBM/dbb/lib/*" -Djava.library.path=/usr/lpp/IBM/dbb/lib helloworld.groovy
  • Alternatively, the DBB toolkit provides a shell script in its bin directory called groovyz that can also be used to invoke Groovy scripts containing DBB APIs. Its utility is that it automatically adds the required DBB class path and library path parameters to the Groovy invocation, thus simplifying the command:
/usr/lpp/IBM/dbb/bin/groovyz helloworld.groovy

When the script is run from the command line, you should see the following output in the console:

Copying source from zFS to PDS . . .
Compiling . . .
Compile successful!  RC=0

Additionally, there should now be a new file in the zFS directory called helloworld.log, which contains the SYSPRINT from the COBOL compilation. You can view it by using the UNIX 'cat' command or by using an EBCDIC editor of your choice.

Congratulations! You have built your first z/OS program using Groovy and DBB.