Wednesday, April 27, 2016

RPG compiler directives

rpg compiler directives including copy include

Having written about the new /SET and /RESTORE compiler directives a while ago, I thought it might be a good idea to do a piece on the other compiler directives.

Compiler directives come in two types:

  • Compiler directive statements:
    • /COPY and /INCLUDE - I assume this is the most commonly used directive and the one I will be discussing in detail.
    • /EJECT - in the compiler listing: Go to top of new page before printing next line.
    • /FREE and /END-FREE - No longer needed, but if your IBM i does not have the PTF for "free format definition" RPG, that was released along with IBM i 7.1 TR7, you will still need these at the start and end of free format code.
    • /SET and /RESTORE - You can learn about these in New RPG compiler directives /SET and /RESTORE.
    • /SPACE - in compiler listing: Advance given number of spaces before printing next line.
    • /TITLE - Heading information that is to appear at the top of each page of a compiler listing.
  • Conditional compilation directives which allow me to select or omit source lines:
    • /EOF
    • /IF, /ELSEIF, /ELSE, and /ENDIF

All the examples given below are in "fully free" RPG, if you are unsure what "fully free" RPG is see here. If you are using an earlier form of RPG, including the "free format definition", where your free code has to start in the eighth or later column and the slash ( / ) of the compiler directives can start anywhere after the seventh column.



01  **free
02  /copy copybook1
03  /copy devsrc,copybook1
04  /copy mylib/devsrc,copybook1

05  /include devsrc,copybook1
06  /include mylib/devsrc,copybook1

The /COPY and /INCLUDE do the same thing, they copy the contents of the given source member into the source member at the point where the copy or include is given. For those of you who remember autoreport and RPT type programs in older versions of RPG will find that RPGLE does not have an equivalent copy method.

I use the /COPY more than any other compiler directive. I have been surprised to learn from two of my colleagues that when they were learning RPG, "way back when", they were told not use them. In my opinion the benefits they bring, especially when coding subprocedures, are great, bringing the ability to use consistent structures and names across programs and procedures. I will explain more about that below.

The snippet of code, above, gives five examples of using the /COPY and /INCLUDE:

Line 2: Just the member name follows the /COPY, this assumes that the member is in the first QRPGLESRC source it finds in my library list.

Line 3: As you know I do not use QRPGLESRC, I use DEVSRC for all of my source members. Therefore, I need to give the source file name, followed by a comma ( , ), and then the member name. This will use COPYBOOK1 from the first DEVSRC it finds in the library list.

Line 4: I always code my /COPY like this. I can be assured that the right member is copied as I have given the library name and source file.

Line 5: Will do the same as line 2.

Line 6: Does the same as line 3.

You can find which /COPY and /INCLUDE members there are in your program or module in the compile listing:

                             / C o p y   M e m b e r s
 Statement  Src  RPG name   <-------- External name -------> CCSID  <- Last change ->
 Number     Id              Library    File       Member            Date     Time
     000400    1 COPYBOOK1  MYLIB      DEVSRC     TESTR_CPY     37  DD/DD/DD TT:TT:TT
          * * * * *   E N D   O F   / C O P Y   M E M B E R S   * * * * *

There is one gotcha if you are using these compiler directives in RPGLE source members with embedded SQL, SQLRPGLE. When compiling these types of programs there is a SQL precompile to validate the SQL statements in the source. If there are variables, etc, in /COPY, or /INCLUDE, members then and the CRTSQLRPGI command is left with its default values the SQL precompile step may error. There is an option in the command, RPGPPOPT, which tells the compiler whether to include the compiler directives before the SQL precompile. The default is *NONE, which is not to include the copy (and include) members. If the value *LVL2 is used then both the copy members are handled before the SQL precompile. IBM's documentation for this can be found here.

I do know you can use the copy directive to copy files from the IFS into your source. But I do not know of anyone who is doing it.

01  /copy /simon/rpg/copybook1.txt
02  /copy '/simon/rpg/copybook 1.txt'

Line 1: Copies from the file copybook1.txt in the IFS directory /simon/rpg .

Line 2: As the file name contains a blank the path and file name has to be enclosed either in single or double quotes. In this example I have used single quotes.

Nested copies and includes could cause a problem, as the default number for nesting is 32. This can be overridden using the COPYNEST keyword in the control options, to a maximum of 2048. For example:

  ctl-opt copynest(99) ;

A potential problem with multiple levels of nesting is that the same copy member may be copied into the program multiple times, which I will discuss later.



I am sure this will come as no surprise to you that these are the testing directives, and they work as you would expect. An /IF can be followed by one or more /ELSEIF, followed by an optional /ELSE, and finished with a /ENDIF. For example:

Below gives you a very simple example of what I can do.

01  /if defined(First)
02  WhatIsDefined = 'First' ;

03  /elseif defined(Second)
04  WhatIsDefined = 'Second' ;

05  /else
06  WhatIsDefined = 'None of the above' ;

07  /endif

Don't worry that I have not written about what the /DEFINED does, I will discuss it below.



By inserting the /EOF directive you are telling the compiler to ignore any source lines that follow. For example:

01  CustRcdFound = *on ;
02  /eof
03  delete CUSTFR ;
04  CustRcdFound = *off ;

Lines 3 and 4 will not be included in the compile.



As expected this two directives are opposites. /DEFINE "sets on" a condition-name, and /UNDEFINE sets it off.

01  /define Condition1

02  /undefine Condition1

As a rule I keep my procedure prototypes definitions in a separate source member. I then use the define to only include the ones I want in my main program. For example:

01  /define GetCustomerState
02  /define ValidateCurrency

03  /include mylib/devsrc,prototypes

04  /undefine ValidateCurrency
05  /undefine GetCustomerState

Lines 1 and 2: I am defining the condition-names to be "on". I always make the condition-name be the same as the procedure.

Line 3: I have used an include rather than a copy just because I can.

Lines 4 and 5: If I define I always undefined after the copy, or include in this case.

If I look in the source member PROTOTYPES in the source file DEVSRC in the library MYLIB for GetCustomerState I find:

01  /if defined(GetCustomerState)
02  dcl-pr GetCustomerState char(8) ;
03    *n char(15) options(*nopass) values ;
04  end-pr ;

05  dcl-ds CustomerStateDs qualified ;           
06    State char(2) ;
07    Country char(3) ;
08    ErrorCode char(3) ;
09  end-ds ;
10  /endif

Line 1: if the condition-name GetCustomerState is defined the compiler will start copying here.

Lines 2 – 4: By keeping my procedure prototype externally like this I can guarantee that it will always be the same, no matter how many other programs I may need to use it in.

Lines 5 – 9: I always put the data structure that contains the data returned by the procedure in here too for the same reason.

Line 10: I need the /ENDIF to stop the copying by the compiler.

Above I mentioned the danger of copying the same member into the program multiple times, either by accident or because the member is /COPY by other copybook members. Below is a really simple example.

This is the main source that happens to have the same /COPY on lines 1 and 2.

01  /copy mylib,devsrc,copybook1
02  /copy mylib,devsrc,copybook1
03  *inlr = *on ;

And this is the member COPYBOOK1:

01  /if defined(Already_Defined)
02    /eof
03  /else
04    /define Already_Defined
05    //Copied from COPYBOOK1
06    *in01 = *on ;
07  /endif

When the compiler processes the first /COPY Already_Defined is not defined, see line 1, therefore, lines 4 to 6 are copied.

The second time the compiler processes the /COPY Already_Defined was defined on line 4 the first time through, therefore the /EOF on line 2 is executed, which stops the copying of this member.

If I look at the compile listing this is what I see:

01  /copy mylib,devsrc,copybook1
    * RPG member name  . . . :  COPYBOOK1
    * External name  . . . . :  MYLIB/DEVSRC(COPYBOOK1)
    * Last change  . . . . . :  DD/DD/DD  TT:TT:TT
02   /if defined(Already_Defined)
04   /else
05     /define Already_Defined
06     //Copied from COPYBOOK1
07     *in01 = *on ;
08   /endif

09  /copy mylib,devsrc,copybook1
    * RPG member name  . . . :  COPYBOOK1
    * External name  . . . . :  MYLIB/DEVSRC(COPYBOOK1)
    * Last change  . . . . . :  DD/DD/DD  TT:TT:TT
10  /if defined(Already_Defined)
11    /eof

12  *inlr = *on ;

I could have coded the COPYBOOK1 member with the slashes anywhere like, see below. In my opinion it makes it too hard to read so I tend to code in the way given above.

01      /if defined(Already_Defined)
02   /eof
03                   /else
04    /define Already_Defined
05  //Copied from COPYBOOK1
06  *in01 = *on ;
07                           /endif


Predefined condition-names

In all the examples I have given above I have defined my own condition-names, but there are a few predefined ones too:

  • Relate to the command being used to compile the object
    • *CRTBNDRPG - comes into play when the CRTBNDRPG command, PDM option 14, is used to generate a program
    • *CRTRPGMOD - used when the CRTRPGMOD command, PDM option 15, is used to generate a module
    • *THREAD_CONCURRENT - condition is defined if the THREAD(*CONCURRENT) is given in the control options, CTL-OPS, or header specifications
    • *THREAD_SERIALIZE - condition is defined if the THREAD(*SERIALIZE) is given in the control options or header specifications
  • Related to the IBM i release the program or module is being compiled for

I can use the command used to compile the object to give source member the appropriate control options:

01  /if defined(*crtbndrpg)
02    ctl-opt option(*nodebugio:*srcstmt:*nounref) dftactgrp(*no) ;
03  /else
04    ctl-opt option(*nodebugio:*srcstmt:*nounref) nomain ;
05  /endif

When working with the release conditions I have to be careful which order I put them in. If the release the IBM i is equal or greater than the release in the /IF statement the following code will be copied. If I was to use:

  /if defined(*v7r1m0) 

The whatever logic was to follow would be copied if compiled in IBM i releases 7.1 and 7.2, but not 6.1 . The example below shows that I must always put the most recent IBM i release first. Notice how the condition-name of the release can be either upper or lower case. And yes the code for 6.1 is in fixed format as free format definitions were never introduced to that release.

01   /if defined(*v7r2m0)
02     dcl-s Text char(10) inz('7.2') ;
03   /elseif defined(*v7r1m0)
04     dcl-s Text char(10) inz('7.1') ;
05   /elseif defined(*V6R1M0)
06  D Text            S             10    inz('6.1')
07   /free
08   /endif

I could combine the two above examples and create code to insert the appropriate control option or header specifications depending upon the release and the command used to compile:

01   /if defined(*v7r1m0)
02     /if defined(*crtbndrpg)
03       ctl-opt option(*nodebugio:*srcstmt:*nounref) dftactgrp(*no) ;
04     /elseif defined(*crtrpgmod)
05       ctl-opt option(*nodebugio:*srcstmt:*nounref) nomain ;
06     /endif

07   /elseif defined(*v6r1m0)
08     /if defined(*crtbndrpg)
09  H option(*nodebugio:*srcstmt) dftactgrp(*no)
10     /elseif defined(*crtrpgmod)
11  H option(*nodebugio:*srcstmt) nomain
12     /endif
13   /endif

When compiled for IBM i version 7.1 and 7.2 lines 1 - 6 are used to determine which CTL-OPT to copy. For version 6.1 lines 7 - 12 are used.

While I have used the compile command definition-names, I have only used the release ones just to see what they did and for this post only.


You can learn more about this from the IBM website:


This article was written for IBM i 7.2, and should work for earlier releases too.


  1. main source member

    FMT ** ...+... 1 ...+... 2 ...+... 3 ...+...
    0001.00 **free
    0002.00 dcl-s Incoming char(30) ;
    0003.00 /copy tstcpy11
    0004.00 *inlr = *on ;
    0005.00 return;

    tstcpy11 copybook source having DSPLY statement
    ...+... 1 ...+... 2 ...+... 3 .
    dsply INCOMING ;

    When I compiled the source, copybook source was expanded and an error message was there in compile listing
    *RNF0257 30 a 000100+ Type de sp{cification de proc{dure principale incorrect ou hors s{quence.

    About /copy compiler directive (peace of content) from book.
    /COPY members are considered fixed-form by default, even if the /COPY directive is coded within a free-form group. If the /COPY member will contain free-form specifications, these must be delimited with /FREE and /END-FREE directives.

    Then I changed the copybook source as below.

    FMT ** ...+... 1 ...+... 2 ...+... 3
    0001.00 /free
    0002.00 dsply INCOMING ;
    0003.00 /End-free

    and again recompiled the main source member.
    Now compilation was successful.

    Ground Rules-Enhanced RPG Compiler:
    •/Free and /End-Free are no longer needed.
    •I & O Specs and RPG Cycle remain Fixed Format.
    •F & D Specs can be mixed together.
    •Free Format and Fixed Format can be intermixed… within the standard coding guidelines, of course!
    •Free Format has sensible defaults.
    •Op Codes are not case specific.
    •Free Format code must be between columns 8-80.
    •Semicolon is necessary at the end of every free format statement ;

    But there was nothing about /copy.

  2. Excellent explanation

  3. /EJECT and /SPACE are supported in free format. They have the same function to control the compiler listing as they do in fixed format.

    1. Thank you for the "heads up", corrections have been made.

  4. A long time ago, I had two programs that were just slightly different. I used one source with compiler directives and compiled it two ways. Worked great!

  5. Reynaldo Dandreb MedillaJuly 31, 2022 at 3:07 AM

    We use compiler directive often, nonetheless this is a good reminder Simon, thanks

  6. Hi Simon, firstly, thanks for this awesome blog. I get so much value from it!

    I've always used the "/undefine" directive with my "/define" when using a copy or include member to define my prototypes. Recently I was questioned why I use it. Truth be told, my answer was "because that's how I was taught to do it". That sent me down the bunny hole of exacly why do I use the "/undefine" directive. I couldn't really find a solid answer. The program will compile without it and I don't see any significant differences in the compile listing or how my program performs. Can you elaborate on the value of the "/undefine" when using it to define procedure prototypes? Thanks!


To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.