@profoundlogic/codermake
v4.0.1
Published
Make wrapper framework for IBM i development that simplifies build rules
Readme
codermake
A make wrapper framework for IBM i development that simplifies build rules and abstracts platform-specific complexity.
Features
- Simplified Rules: Write clean, maintainable build rules in
Rules.mkfiles - Platform Agnostic: Same rules work on IBM i and Unix-like systems (Linux, macOS, FreeBSD, etc.)
- Automatic Discovery: Automatically finds and processes the Rules.mk files of every library directory in your project
- Recipe Inference: Automatically determines build recipes based on file extensions
- Remote Builds: Build from Unix-like systems to IBM i via SSH
- Compile Options: Set default compile parameters (e.g.
TGTRLS,DBGVIEW) per project via.codermake/config.json
Installation
npm install -g @profoundlogic/codermakeQuick Start
1. Set up environment variables
On IBM i (local build):
export BUILD_LIBRARY=DEVLIBOn Unix-like systems (remote build via SSH):
export IBMI_BUILD_LIBRARY=DEVLIB
export IBMI_HOST=ibmi.example.com
export IBMI_USER=devuser
export IBMI_KEY=/path/to/ssh/key # OptionalMulti-library output mode (alternative to BUILD_LIBRARY / IBMI_BUILD_LIBRARY):
# Map the project's library directories to IBM i libraries
export CODERMAKE_LIBRARY_MAP="LIBA=APPLIBA LIBB=APPLIBB"
# Or use the natural mapping (LIBA → LIBA, LIBB → LIBB)
export CODERMAKE_LIBRARY_MAP=auto
# Optional: replace the user portion of the IBM i library list at build time
export CODERMAKE_LIBRARY_LIST="APPLIBA APPLIBB"CODERMAKE_LIBRARY_MAP is mutually exclusive with BUILD_LIBRARY / IBMI_BUILD_LIBRARY. See Multi-library output below.
2. Create a library directory and its Rules.mk
Each top-level directory of the project is an IBM i library, holding one directory per source physical file:
project/
└── mylib/
├── Rules.mk
├── qrpglesrc/
│ └── hellor.rpgle
└── qddssrc/
└── hellod.dspfCreate mylib/Rules.mk:
# Display file
hellod.file: qddssrc/hellod.dspf
# Simple program with display file
hellor.pgm: qrpglesrc/hellor.rpgle qddssrc/hellod.dspf | hellod.fileSee Project Layout for the full layout.
3. Build
codermakeRules.mk Syntax
Basic Rule Format
target: prerequisitesThe first normal prerequisite is the source that creates the object, and it
is what selects the compile command codermake runs. Every normal prerequisite
after it is a dependency: it forces a rebuild when it changes but never changes
the command. Order-only prerequisites (after |) do neither — they only have to
exist first.
Paths in Rules.mk
A Rules.mk belongs to the library directory it sits in, and its names are resolved against that library:
| Written as | Means |
|---|---|
| hellor.rpgle | A source file in the Rules.mk's own directory |
| qrpglesrc/hellor.rpgle | A source file in this library (mylib/qrpglesrc/hellor.rpgle) |
| srclib/qrpglesrc/hellor.rpgle | A source file in another library directory, srclib |
| hellod.file | An object in this library |
| dtalib/custp.file | An object in another library directory, dtalib |
A path is a cross-library path when its first segment names a library
directory. Names containing $ or # use makefile escaping: write $$ and \#
(d$$pgm.pgm, h\#pgm.pgm). On the command line and in --list-targets output
they appear literally ('h#pgm.pgm'). A two-segment object name whose first segment is not a library
directory is reported as an invalid library qualifier.
The examples below are written for a library-level Rules.mk (mylib/Rules.mk).
Supported File Types
Output Objects:
.pgm- Program.module- Module.srvpgm- Service Program (from modules, SQL procedures withPROGRAM TYPE SUB, or SQL UDFs).file- File (display, physical, logical, printer, SQL table/index/view).menu- Menu.msgf- Message File.bnddir- Binding Directory
Source Files:
.rpgle- ILE RPG source.sqlrpgle- SQL RPG source.cblle- ILE COBOL source (CRTBNDCBL / CRTCBLMOD).sqlcblle- ILE COBOL source with embedded SQL (CRTSQLCBLI).cbl- OPM COBOL source (CRTCBLPGM).cbl38- System/38 compatible COBOL source (QSYS38/CRTCBLPGM).sqlcbl- OPM COBOL source with embedded SQL (CRTSQLCBL).cpy- COBOL copybook (target of aCOPYstatement).clle- ILE CL source.cl- OPM CL source.clp- OPM CL source (alternate).clp38- System/38 compatible OPM CL source (QSYS38/CRTCLPGM).rpg- RPG/400 (RPG III) source (CRTRPGPGM).rpg38- System/38 compatible RPG III source (QSYS38/CRTRPGPGM).dspf- Display file DDS.pf- Physical file DDS.lf- Logical file DDS.prtf- Printer file DDS.dspf38- System/38 compatible display file DDS (QSYS38/CRTDSPF).pf38- System/38 compatible physical file DDS (QSYS38/CRTPF).lf38- System/38 compatible logical file DDS (QSYS38/CRTLF).prtf38- System/38 compatible printer file DDS (QSYS38/CRTPRTF).table.sql- SQL table definition.index.sql- SQL index definition.view.sql- SQL view definition.proc.sql- SQL procedure definition (*PGM, or*SRVPGMwithPROGRAM TYPE SUB).udf.sql- SQL function definition (CREATE FUNCTION ... LANGUAGE SQL→*SRVPGM).trg.sql- SQL trigger definition (CREATE TRIGGER→*PGM).json- Rich Display File (RDF) JSON (when first prerequisite of.filetarget).msgf- Message file CL commands (when first prerequisite of.msgftarget).bnddir- Binding directory CL commands (when first prerequisite of.bnddirtarget).exportsor.bnd- Service program export list
Note: .msgf and .bnddir files serve dual purposes:
- As source: When appearing as the first normal prerequisite of a matching target type (e.g.,
app.msgf: app.msgf) - As object: In all other contexts (e.g., as prerequisites of other target types or non-first prerequisites)
Examples
Simple Program:
mypgm.pgm: qrpglesrc/mypgm.rpgleService Program:
mymod.module: qrpglesrc/mymod.rpgle
mysrvpgm.srvpgm: mymod.module qsrvsrc/myexports.exports
# Without export list (exports all procedures via EXPORT(*ALL)):
mysrvpgm.srvpgm: mymod.moduleProgram from Modules (with automatic binding):
calc.module: qrpglesrc/calc.rpgle
calc.pgm: calc.module utils.srvpgm # BNDSRVPGM(utils) added automatically
calcd.pgm: calcd.module | app.bnddir # BNDDIR(app) added automatically.srvpgm and .bnddir prerequisites work as either normal or order-only; the automatic BNDSRVPGM()/BNDDIR() parameters are generated either way. This only applies to module-based programs (CRTPGM/CRTSRVPGM), not source-based programs (e.g., CRTBNDRPG).
Program with Dependencies:
hellor.pgm: qrpglesrc/hellor.rpgle hellod.file custr.srvpgmNote: This is a source-based program (CRTBNDRPG), so custr.srvpgm is a build-ordering dependency only — no BNDSRVPGM() parameter is generated. Binding is typically handled via a BNDDIR control specification in the RPG source.
Rich Display File from JSON:
mydisplay.file: qddssrc/mydisplay.jsonCL Programs:
# ILE CL
hellocl.pgm: qclsrc/hellocl.clle
# OPM CL
simpgm.pgm: qclsrc/simpgm.clp
# CL Module
mathmod.module: qclsrc/mathmod.clle
mathpgm.pgm: mathmod.modulePrinter File:
helloprt.file: qddssrc/helloprt.prtfFile Objects That Depend on Other Files:
IBM i *FILE objects frequently depend on other files, and on more than one of
them: a logical file is based on one or more physical files, and a file of any
type can take its field definitions from a field reference physical file (DDS
REF/REFFLD). List the dependency's source as a normal prerequisite so
the dependent file is recompiled when that source changes, and the dependency's
file object as an order-only prerequisite so it exists on IBM i at compile
time:
# Field reference physical file
fldref.file: qddssrc/fldref.pf
# Physical file whose fields come from the field reference file (REF)
custp.file: qddssrc/custp.pf qddssrc/fldref.pf | fldref.file
ordp.file: qddssrc/ordp.pf qddssrc/fldref.pf | fldref.file
# Logical file over a physical file
custl.file: qddssrc/custl.lf qddssrc/custp.pf | custp.file
# Join logical file over two physical files
custjoin.file: qddssrc/custjoin.lf qddssrc/custp.pf qddssrc/ordp.pf | custp.file ordp.file
# Display file with field references to two physical files (REF and REFFLD)
custd.file: qddssrc/custd.dspf qddssrc/fldref.pf qddssrc/ordp.pf | fldref.file ordp.file
# SQL view over a DDS physical file
custvw.file: qsqlsrc/custvw.view.sql qddssrc/custp.pf | custp.fileBecause the first prerequisite is the source, custl.file compiles with
CRTLF even though the rule also lists a .pf — the physical file's DDS is a
dependency, not the source.
System/38 Compatible DDS:
# .pf38/.lf38/.dspf38/.prtf38 use dedicated recipes that call the QSYS38
# CRTPF/CRTLF/CRTDSPF/CRTPRTF command variants. The temporary source member is
# tagged with the matching System/38 source type (PF38/LF38/DSPF38/PRTF38),
# derived automatically from the file extension.
custp38.file: qddssrc/custp38.pf38
custl38.file: qddssrc/custl38.lf38 custp38.file
hello38.file: qddssrc/hello38.dspf38
rpt38.file: qddssrc/rpt38.prtf38RPG III and System/38 CL Programs:
# RPG/400 (RPG III) — CRTRPGPGM ... REPLACE(*YES)
myrpg.pgm: qrpgsrc/myrpg.rpg
# System/38 compatible RPG III — QSYS38/CRTRPGPGM (delete-then-create)
myrpg38.pgm: qrpgsrc/myrpg38.rpg38
# System/38 compatible OPM CL — QSYS38/CRTCLPGM (delete-then-create)
mycl38.pgm: qclsrc/mycl38.clp38.rpg, .rpg38, and .clp38 cannot compile directly from stream files, so
codermake creates a temporary source member (QRPGSRC/QCLSRC) before
compiling. RPG III /COPY directives always reference source members, so any
/COPY prerequisite is created as a source member too. List each /COPY
target as a prerequisite; its path in the source tree determines the source
file (parent directory) and the member name (base name). The member is created
in the program's own library — or, in multi-library output mode, in the library
its library directory is mapped to, when that directory has a map entry:
# QCPYSRC,SHARED is staged as a source member (QCPYSRC/SHARED) before compiling
myrpg.pgm: qrpgsrc/myrpg.rpg qcpysrc/shared.rpgCOBOL Programs:
# ILE COBOL — CRTBNDCBL
hellocbl.pgm: qcbllesrc/hellocbl.cblle
# ILE COBOL module bound into a program
cblmod.module: qcbllesrc/cblmod.cblle
cblpgm.pgm: cblmod.module
# ILE COBOL with embedded SQL — CRTSQLCBLI
sqlcbl.pgm: qcbllesrc/sqlcbl.sqlcblle
# OPM COBOL — CRTCBLPGM ... REPLACE(*YES)
opmcbl.pgm: qcblsrc/opmcbl.cbl
# System/38 compatible COBOL — QSYS38/CRTCBLPGM (delete-then-create)
opm38.pgm: qcblsrc/opm38.cbl38
# OPM COBOL with embedded SQL — CRTSQLCBL
sqlopm.pgm: qlblsrc/sqlopm.sqlcblCOBOL Copy Sources (Compile Time Includes):
List each copybook as a prerequisite: that ships it to IBM i for a remote build and makes a change to it rebuild the program.
# ILE COBOL: the COPY statement may name an IFS path, relative to the project root
# COPY 'mylib/qcpysrc/cpybook.cpy'.
# or a source member, which codermake rewrites to that path before compiling
# COPY CPYBOOK OF QCPYSRC.
cblcpy.pgm: qcbllesrc/cblcpy.cblle qcpysrc/cpybook.cpy
sqlcpy.pgm: qcbllesrc/sqlcpy.sqlcblle qcpysrc/cpybook.cpy
# OPM COBOL compiles from a source member, so the COPY statement is left as
# written and QCPYSRC/CPYBOOK is created before compiling
opmcpy.pgm: qcblsrc/opmcpy.cbl qcpysrc/cpybook.cpy.cbl, .cbl38, and .sqlcbl cannot compile directly from stream files, so
codermake creates a temporary source member (QCBLSRC for .cbl/.cbl38,
QLBLSRC for .sqlcbl) before compiling, plus a member for every COPY
prerequisite. See design/COBOL_COPY_PREPROCESSING.md.
SQL Objects:
employee.file: qsqlsrc/employee.table.sql
empaudit.file: qsqlsrc/empaudit.table.sql
empview.file: qsqlsrc/empview.view.sql employee.file
empname.file: qsqlsrc/empname.index.sql employee.file
getcount.pgm: qsqlsrc/getcount.proc.sql employee.file # SQL procedure -> *PGM
getcounts.srvpgm: qsqlsrc/getcounts.proc.sql employee.file # SQL procedure (PROGRAM TYPE SUB) -> *SRVPGM
getname.srvpgm: qsqlsrc/getname.udf.sql employee.file # SQL UDF (LANGUAGE SQL) -> *SRVPGM
emptrg.pgm: qsqlsrc/emptrg.trg.sql employee.file empaudit.file # SQL trigger -> *PGMSQL routines and triggers are built with RUNSQLSTM: codermake only runs the statement, and IBM i creates and names the resulting object. Name SQL routines to match the build target so the generated *PGM/*SRVPGM object name lines up with the rule. For triggers, use the trigger name or PROGRAM NAME to align the generated *PGM with the .pgm target.
Order-Only Prerequisites:
mypgm.pgm: qrpglesrc/mypgm.rpgle | mybnddir.bnddirOrder-only prerequisites must exist before building the target but do not trigger a rebuild when they change. Note: for source-based programs like the above (CRTBNDRPG), no automatic BNDDIR() parameter is generated — the binding directory is typically specified via a BNDDIR control specification in the RPG source instead.
Command-Line Options
codermake [options] [targets...]Options
--help- Show help message--version- Show version--debug- Enable debug output--print-rules- Print preprocessed rules and exit--print-makefile- Generate standalone Makefile for debugging--list-targets[=FORMAT]- Report which targets a build would affect, without building anything.FORMATistext(default),jsonortsv. Remote builds only (not available when running on IBM i)--license-show- Show license status--license-set=<key>- Install a license key
All other options are passed through to GNU Make. See make --help for additional options.
Examples
Build all targets:
codermakeBuild specific target:
codermake mylib/hellor.pgm # library-qualified
codermake hellor.pgm # bare name: the first library with that targetParallel build:
codermake -j4Dry run:
codermake -nClean:
codermake cleanDebug preprocessing:
codermake --print-rulesListing Affected Targets
--list-targets reports the objects a build would create or replace — the ones
whose source changed, plus everything that depends on them — without compiling
anything and without connecting to the IBM i.
codermake --list-targets3 targets affected (dependency order)
TARGET LIBRARY OBJECT TYPE SOURCE TGTRLS REASON
LIBB/srvb.module APPLIBB srvb *module LIBB/qrpglesrc/srvb.rpgle changed
LIBB/srvb.srvpgm APPLIBB srvb *srvpgm LIBB/srvb.module dependent
LIBA/crossbnd.pgm APPLIBA crossbnd *pgm LIBA/crossbnd.module dependentREASON says why each object is in the list:
changed— one of this object's own sources changed: the source it is compiled from, a/COPYmember or copybook it includes, its export list.dependent— it only needs building because another object in the build changed: a*moduleit is bound from, a*fileit compiles against, or a file whose DDS it references for field definitions.missing— the object does not exist yet.
The changed rows are the objects you actually edited; the dependent rows are
the reach of those edits. So editing one field reference physical file's DDS
reports that file as changed and every file that takes its fields from it as
dependent, rather than reporting all of them as direct changes:
TARGET LIBRARY OBJECT TYPE SOURCE TGTRLS REASON
devlib/fdref.file DEVLIB fdref *file devlib/qddssrc/fdref.pf changed
devlib/fdcust.file DEVLIB fdcust *file devlib/qddssrc/fdcust.pf dependent
devlib/fdord.file DEVLIB fdord *file devlib/qddssrc/fdord.pf dependentThis works off the prerequisites in Rules.mk: a referenced file's DDS is a
normal prerequisite of the files built over it, and codermake knows that member is
what fdref.file is compiled from. A shared source that no rule builds into an
object — a /COPY member — has no object to attribute the change to, so each
program that lists it is changed. Every listed object needs rebuilding whatever
its reason.
TGTRLS is blank unless the project sets a target release and the object's
compile command accepts one, as above.
All three output formats carry the same seven fields in the same order, always — the columns never move or disappear.
The answer is make's own out-of-date determination, so it is exactly what a build from this checkout would do.
Remote builds only. --list-targets is available when building from a
Unix-like host (Linux, macOS), where codermake tracks build state as touch
markers under build/. Running codermake locally on IBM i there are no markers —
the build targets are the objects themselves, which codermake -t cannot
baseline — so the option is refused there with an explanation.
The comparison being against those local markers rather than the IBM i objects is what lets the answer come back immediately with no connection. The flip side is that the markers describe what this checkout has built: if an object is deleted on the IBM i by hand, or a second checkout builds into the same library, the markers do not know, and neither listing nor a build will notice.
Two more consequences worth knowing:
- List before you build. After a successful build everything is up to date, so the list is empty. Listing again afterwards is a cheap way to confirm that everything intended was built.
- Listing changes nothing. No object is created, no marker is touched, no compile listing is discarded. Running it twice gives the same answer.
Machine-readable output
--list-targets=tsv prints one record per line with no header, and
--list-targets=json prints a JSON document. Both carry the same seven fields
in this order:
| Field | Example | Notes |
|---|---|---|
| target | LIBA/app.pgm | Can be passed straight back to codermake as a target |
| library | APPLIBA | The IBM i library this invocation builds it into |
| object | app | Bare object name, as written in Rules.mk |
| type | *pgm | *pgm, *module, *srvpgm, *file, *menu, *msgf, *bnddir |
| source | LIBA/qrpglesrc/app.rpgle | The primary input; for bound objects, the object it is built from |
| targetRelease | V7R4M0 | From .codermake/config.json, where the compile command accepts TGTRLS |
| reason | dependent | changed (one of its own sources changed), dependent (another object it depends on changed) or missing |
The first field is a name codermake accepts as a target argument, so a list can
be narrowed and fed back in — including with a different CODERMAKE_LIBRARY_MAP,
to build the same objects into staging libraries:
codermake --list-targets=tsv > affected.tsv
codermake $(cut -f1 affected.tsv | tr '\n' ' ')The exit status is 0 whether or not anything is affected, so the output can be
piped unconditionally. --list-targets cannot be combined with -t, -q or
clean.
Compile Options
By default, codermake supplies a fixed set of parameters to each IBM i compile
command. You can set your own defaults — such as a target release or debug
view — with an optional configuration file at .codermake/config.json in your
project root (the directory you run codermake from). No file is required; when
it is absent, nothing changes.
{
"targetRelease": "V7R4M0",
"compileOptions": {
"crtbndrpg": { "dbgview": "*all" },
"crtrpgpgm": { "option": ["*srcdbg"] },
"runsqlstm": { "commit": "*chg" },
"crtsqlrpgi": { "dbgview": "*source", "compileopt": { "optimize": "*full" } }
}
}targetReleasesetsTGTRLS, applied to each compile command that accepts it.compileOptionsis keyed by CL command name (case-insensitive, e.g.crtbndrpg,qsys38/crtclpgm). Each entry adds keyword parameters to that command. An entry applies to every recipe that uses the command.
Some parameters are reserved — codermake controls them because changing
them would break the build (e.g. PGM, SRCSTMF, OUTPUT(*PRINT), TGTCCSID)
— and setting a reserved parameter is reported as an error. OPTION and
COMPILEOPT are augmentable: your values are merged with the parts
codermake requires (for example OPTION(*SOURCE ...) always keeps *SOURCE).
Verify what will run without compiling:
codermake --print-makefile # shows the effective compile parameters
codermake -n <target> # dry-run the expanded compile commandProject Layout
Every top-level directory of the project is an IBM i library directory. A
library directory holds one directory per source physical file, with one file
per member (<lib>/<srcpf>/<member>.<ext>), and can also hold source files
directly — a binding directory's or message file's CL source, for instance
(<lib>/app.bnddir). A library that receives objects has a Rules.mk:
project/
├── srclib/ # Source library: source files, no Rules.mk
│ ├── qrpglesrc/
│ │ └── custr.rpgle
│ ├── qddssrc/
│ │ ├── custp.pf
│ │ └── custd.dspf
│ └── qcpysrc/
│ └── custpr.rpgle
├── dtalib/ # Object library for files
│ └── Rules.mk
├── objlib/ # Object library for programs
│ ├── Rules.mk
│ └── app.bnddir
├── applib/ # A library holding both its source and its objects
│ ├── Rules.mk
│ └── qrpglesrc/
│ └── apppgm.rpgle
├── tmp/ # Build outputs (gitignored)
│ └── logs/ # Compiler output logs
└── build/ # Dummy IBM i objects (Unix-type systems only, gitignored)# dtalib/Rules.mk
custp.file: srclib/qddssrc/custp.pf
# objlib/Rules.mk
custd.file: srclib/qddssrc/custd.dspf
custr.pgm: srclib/qrpglesrc/custr.rpgle srclib/qcpysrc/custpr.rpgle | custd.file dtalib/custp.file
app.bnddir: app.bnddir
# applib/Rules.mk
apppgm.pgm: qrpglesrc/apppgm.rpgle | objlib/app.bnddirA library's rules live in <lib>/Rules.mk, or in a Rules.mk inside one of its
source-file directories (<lib>/qrpglesrc/Rules.mk, where a bare source name
such as mypgm.rpgle is a file in qrpglesrc/). A library can use both. A
Rules.mk at the project root is an error.
Hidden directories and node_modules/, tmp/, build/ and test/ are not
library directories. Other top-level directories — docs/, say — are library
directories that simply hold no rules.
By default every library's objects build into a single output library (set
via BUILD_LIBRARY / IBMI_BUILD_LIBRARY). To build each library directory into
its own IBM i output library, see Multi-library output
below.
Multi-library output
CODERMAKE_LIBRARY_MAP enables per-library output:
export CODERMAKE_LIBRARY_MAP="LIBA=APPLIBA LIBB=APPLIBB"Each library directory builds into the IBM i library named on the right side of =. Every library directory with a Rules.mk needs an entry; source-only library directories need none. The sentinel value auto enables a "natural" mapping where each library directory with a Rules.mk maps to a library of the same name:
export CODERMAKE_LIBRARY_MAP=autoCODERMAKE_LIBRARY_MAP is mutually exclusive with BUILD_LIBRARY / IBMI_BUILD_LIBRARY. Setting both, or neither, is an error.
Cross-library object references
A Rules.mk in one library can refer to objects produced by another library using the qualifier syntax <libraryDir>/<bareobj>.<ext>:
# In LIBA/qrpglesrc/Rules.mk
crossbnd.pgm: crossbnd.rpgle | LIBB/utils.srvpgmThe qualifier works on both prerequisites and targets. Source files in another library use the 3-segment form (LIBB/qcpysrc/shared.rpgle).
A target can also be qualified to redirect the output library — useful when a single source tree compiles into multiple deployment libraries:
# Source in LIBA's tree, output goes to LIBB
LIBB/installer.pgm: installer.rpgleOptional: control the IBM i library list at build time
CODERMAKE_LIBRARY_LIST (only valid with CODERMAKE_LIBRARY_MAP) replaces the user portion of the IBM i library list with the listed libraries in declared order:
export CODERMAKE_LIBRARY_LIST="APPLIBA APPLIBB"This is useful when BNDSRVPGM() and BNDDIR() parameters need to resolve unambiguously across mapped libraries. Unqualified service-program and binding-directory references resolve via the library list at bind/runtime, by design — this preserves the developer workflow of placing a private library copy higher on the runtime liblist to override without rebuilding.
When CODERMAKE_LIBRARY_LIST is not set, codermake leaves the build job's library list untouched (whatever the job description / profile inherits).
Portability across modes
The same Rules.mk works in both single-library output mode (BUILD_LIBRARY / IBMI_BUILD_LIBRARY) and multi-library output mode (CODERMAKE_LIBRARY_MAP). In single-library mode, qualifier syntax collapses to the single output library — useful for containerized agentic builds that use one disposable library while the production IBM i build uses per-library output.
RPG /COPY and COBOL COPY resolution
Member-style RPG /COPY and /INCLUDE directives and COBOL COPY ... OF statements
resolve against the library directories:
- A library-qualified reference (
/COPY LIBB/QCPYSRC,MEMBER) is looked up in that library directory. - An unqualified reference (
/COPY QCPYSRC,MEMBER,COPY MEMBER OF QCPYSRC) is looked up in each library directory in turn; a bare RPG member (/COPY MEMBER) is looked up in each library'sqrpglesrc/. When a member exists in more than one library, the first is used and a warning names them all.
Set CODERMAKE_INCLUDE_PATH to choose which library directories are searched for
unqualified references, and in what order:
export CODERMAKE_INCLUDE_PATH="LIBA LIBB LIBC"A /COPY or COPY that names an IFS path is left as written and is relative to
the project root (/COPY mylib/qcpysrc/member.rpgle).
How It Works
- Platform Detection: Detects IBM i vs Unix-like system
- Rules Discovery: Finds the library directories and the
Rules.mkfiles in them - Preprocessing: Transforms simplified rules into full GNU Make syntax
- Recipe Inference: Automatically determines build recipes from file extensions
- Execution: Invokes GNU make with generated rules
RPG /COPY and /INCLUDE Preprocessing
codermake automatically handles legacy RPG /COPY and /INCLUDE directives written in source member style. Source member references are converted to IFS stream file paths during compilation, allowing the same source to work in both source member and stream file environments.
See design/RPG_COPY_PREPROCESSING.md for details.
COBOL COPY (Copy Source) Handling
codermake resolves COBOL copy sources the way each compiler expects. ILE COBOL
(.cblle, .sqlcblle) compiles from a stream file: an IFS style COPY resolves
against the compile-time include path, and a source member style COPY MBR OF
QCPYSRC is rewritten to the copybook's IFS path before compiling. OPM COBOL
(.cbl, .cbl38, .sqlcbl) compiles from a source member, so the statement is
left as written and codermake creates the member it names. Either way the
original source keeps working on a system that compiles from source members.
See design/COBOL_COPY_PREPROCESSING.md for details.
Requirements
- Node.js: >= 22.0.0
- GNU Make: >= 4.0
- SSH: For remote builds from Unix-like to IBM i
Author
Profound Logic Software
