1,619 Compiler Messages, Four Real Causes: A CA 2E to RPGLE Compile Reference
What actually happens when a Synon model becomes a compiled RPGLE program — and how to read the wall of errors when it doesn't
CA 2E (Synon) does not write RPG. It writes a model — files, functions, action diagrams — and a code generator turns that model into RPG source, then a separate step turns that source into a compiled object. Most of the confusion people have converting CA 2E work to RPGLE comes from not knowing where the seam between those two steps is. This is a reference for that seam: the path from model to object, what it looks like when it breaks, and how to read a compile listing with over a thousand messages in it without treating each one as a separate problem.
No library names, program names, or field names below are real. They've been left out entirely rather than replaced with placeholders, because the mechanics and the error categories don't need them to make sense.
Two things to know before you touch a model
Generate and Create are different steps, and only one of them needs the model to be right.
"Generate" turns a function's action diagram into RPG source and writes it to a member. It does not compile anything. "Create" compiles and binds that source into an object. CA 2E's menu lets you do both in one submitted job, which is convenient right up until that job fails, because the failure message tells you almost nothing.
Target HLL "RPG IV" does not mean free-format.
This is the part that surprises people coming from a "just convert it to RPGLE free" mindset. Setting a function's target high-level language to RPG IV makes CA 2E generate traditional fixed-format RPG — column-based calculation specs, sequence numbers, no free-format delimiters. It compiles as an RPGLE member type, and ILE will bind it like any other RPGLE program, but the source itself reads like an older RPG dialect with a newer header. If you want genuinely free-format output, that is a second, deliberate step after the generated source compiles clean — never before. Free-format conversion tools rewrite syntax; they don't fix logic. Feed one a program that doesn't compile and you get free-format syntax wrapped around the same bug, which is now harder to spot because it looks new.
So the realistic pipeline is: model, then generate, then fixed-format RPG IV source, then compile clean, and only then — if you actually want free-format — convert the syntax. Not model straight to free-format RPGLE in one step. Nothing in the generator does that for you.
The path from model to object
1. Get the model on the library list. The generation commands need to find both the model itself and the separate generation library it automatically maintains alongside it. If the underlying product library isn't available, you get a blunt message before you even get near your own code, something to the effect of the product library not being installed for high-level-language generation.
2. Copy the model if you don't want to touch the real one. There's a menu option specifically for commands that copy a model, and the command behind it lets you copy from one model to a new one while telling it not to create any objects — just the definitions. That gives you a sandbox to generate and compile in without touching anything the real model owns.
3. Set the function's target language. Inside the copied model, find the function you're working on and set its target high-level language to RPG IV. This is a setting on the individual function, not on the model as a whole — different functions in the same model can target different languages.
4. Generate the function. From the screen that lists a model's functions, there's an option to generate. This writes the RPG IV source member into the model's generation library, whether or not anything gets compiled afterward. The member existing is not evidence the program works. It's evidence the generator ran.
5. Submit the model create request. There's a follow-on option that submits generation and compilation and binding together as one batch job.
If that job finishes clean, you have a working object and nothing below this line matters. If it doesn't —
When the submit job dies, stop debugging the submit job
The message you get back is short: a job identifier, and a note that it ended abnormally.
That's it. No compiler message, no line number, no indication of which of the several things inside a generate-then-compile-then-bind job actually failed. You can chase this through job logs and the model's own error lists, and sometimes that's the right call if the model itself is broken — a missing access path, an undefined function reference. But most of the time, the model is fine and the generated source has a problem the compiler would tell you about immediately, if you asked the compiler directly instead of through that wrapper.
You already have the source. The generate step wrote it, independent of whether the create step's compile succeeded. Go find it with the standard member-list utility, pointed at the generation library and the source file the generator writes to.
The member is sitting there — generated, uncompiled or compiled-and-failed. Compile it directly from that member list. Now you're not waiting on a batch job to tell you it died. You're looking at the actual compiler output, the same listing you'd get for hand-written source. That's the pivot: the moment the black-box submit job fails, drop out of the model layer entirely and work with the generated source like it's just RPGLE, because at this point it is.
This one compile threw:
Message Totals:
Information (00) . . . . . . : 48
Warning (10) . . . . . . : 0
Error (20) . . . . . . : 995
Severe Error (30+) . . . . . . : 576
-----------------------------------
Total . . . . . . . . . . . : 1619
Sixteen hundred and nineteen messages on a program that, days earlier, had been generating and running fine somewhere else in the model's history. Nobody rewrote 1,619 things overnight. Something small broke, and the compiler reports every downstream consequence of that one thing as its own message.
Reading the listing without reading 1,619 lines
Don't start at the top. Two places matter first.
The final summary, shown above, tells you how bad it is in aggregate: mostly error and severe error, no warnings, means something structural, not a style nitpick.
The message-frequency section — the same compile listing groups every message by its ID and gives you a count. This is the part almost nobody scrolls down to look at, and it's the part that turns 1,619 lines into an actual to-do list. In this listing, ten distinct message types accounted for everything:
- A handful of messages saying that all record formats for an externally described file were ignored or dropped due to error, and the file itself was ignored.
- A handful more saying a file-name or record-name entry wasn't valid, so the input record was ignored.
- A handful saying a record format name in an externally described file was already defined, so the format was ignored.
- Then the bulk of the volume — hundreds of occurrences of "the name or indicator is not defined," hundreds more of "the field on the calculation specification is not numeric, so the specification is ignored," dozens of "the move operation has operands that are not compatible," dozens of "expression contains an operand that is not defined," and a couple of "the types on either side do not match in the EVAL operation."
- Rounding it out, a few hundred lower-severity messages about matching-fields and control-level entries defaulting to blank because they weren't set to valid values.
Two message types, roughly ten occurrences between them, are root causes. Everything else — the other 900-plus occurrences — is the compiler dutifully reporting that fields it can no longer see are, in fact, no longer defined. Once a record format gets dropped, every field from that format that the program touches throws its own "not defined," and every operation built on that undefined field throws another message about incompatible types or non-numeric operands. One dropped file produces a hundred symptoms. Fix the file, and the hundred symptoms go with it.
This is the actual skill in triaging a CA 2E-generated compile: read the frequency table before the line-by-line, find the message types whose description describes a structural problem — a file, a format, a name resolution — fix those first, and recompile before touching anything the cascade produced. Chasing the cascade individually is how a five-hour fix turns into a two-day one.
The four root causes, described rather than named
1. The library list at compile time isn't the library list the model's own job used
The model's own submit job runs with its own library list, built from the model's configuration. When you compile the generated source yourself, you're using whatever library list your own session has — and if the physical and logical files the generated source references aren't resolvable from there, the compiler doesn't fail loudly. It fails quietly, reporting the file as ignored, and then reports every field from that file as undefined for the rest of the listing. This produces exactly the shape above: a couple of "file ignored" messages and hundreds of cascading "not defined" messages that look unrelated but all trace back to one file the compiler couldn't find.
The fix is to put the actual data library — the one holding the physical and logical files the function touches, not just the model's own libraries — on the library list before compiling standalone. This is the single highest-leverage thing to check first when a compile like this looks catastrophic.
2. A record format collides with itself
One message type in particular — a record format in an externally described file already defined, so the format gets ignored — shows up when the same format name resolves twice. Usually that's a leftover generated member from a prior attempt still sitting in the same source file, or two access paths on the same file generating under the same format name. Deleting the stale member and regenerating clean is faster than trying to patch around a duplicate definition.
3. The generated instruction references a name the file doesn't actually expose
Somewhere in the generated code, a read-by-key operation was pointed at the generator's own internal working name for an access path, rather than the actual key-list name that the underlying logical file exposes externally. Most of the time these line up and you never notice — the generator's internal label and the file's real key-list name happen to match. When they don't, the fix is a one-line swap of the operand to the file's real key-list name. You'd never find this by reading "the name or indicator is not defined" in isolation, since that message fires on dozens of unrelated lines too. You find it by noticing this specific line sits right next to the file that came back "ignored," and checking the logical file's actual externally-defined key-list name against what the generator wrote into the calculation.
4. Fields exist in the table, not in the format the program is reading
A program reads by key against a logical file — an index — rather than the physical file directly, which is normal; that's what indexes are for. But if a couple of fields exist on the underlying physical file and were never carried onto that particular logical file's externally described format, the generated program can reference them by name — because the model knows about the physical file — while the compiler, working only from the logical file's actual record format, correctly says they don't exist.
There are two honest fixes here. Either change which file the program keys off of, to one that does carry those fields, or confirm — actually confirm, not assume — that the values are already being retrieved elsewhere in the program from a different file, and only then drop the reference. In the case that produced this: both fields were already being pulled from two other files the program had open. The reference through the index was redundant, not load-bearing, so removing it was correct. If it hadn't been redundant, the right fix would have been changing which file the read targets, not deleting the field.
An estimation table that actually holds up
If you're scoping more than one or two of these, per-program time varies enormously by file usage, and a flat "X hours per program" estimate will be wrong in both directions. Track three numbers separately, because they don't correlate the way you'd expect: how long the conversion itself took, how long fixing the resulting compile errors took, and how much testing effort is still outstanding.
The first program in a batch is not representative of the rest. It takes the longest on both conversion and compile-fixing, because you're still learning the model's conventions and still learning which message types are structural versus cascade. The second and third go faster — not because the code is simpler, but because you've already built the pattern library for what a "file ignored" cascade looks like versus a genuine logic gap. A program with heavier file usage, or a display file involved, will cost more again regardless of where it sits in the sequence. Budget the first one generously and use it to calibrate, not to extrapolate.
The reference version of all this
- Generate writes source. Create compiles it. They can run together in one submitted job, but they fail independently — know which one broke.
- Target high-level language "RPG IV" gets you fixed-format RPG IV. Free-format is a separate, deliberate conversion step you run after the source compiles clean, never before.
- When a submitted create job ends abnormally, don't dig through the job log for a reason. Go straight to the generation library, find the source the generate step already wrote, and compile it yourself. You'll get real compiler diagnostics instead of a batch-job status.
- On a large error count, read the final summary and the message-frequency table before anything else. Look for structural message types — file and format resolution — before touching the hundreds of cascade messages they cause.
- A file reported as ignored, or a format reported as already defined, are library-list and stale-member problems, not code problems. Fix those first; most of the rest disappears on the next compile.
- A field the model knows about isn't necessarily a field the file format you're reading exposes. Read against the file that actually carries what you need, or confirm the value already exists elsewhere before you delete the reference.
- Effort on the first converted program tells you almost nothing about the tenth. Track conversion time and compile-fix time separately per program if you're estimating a batch.
Jaya Krushna Mohapatra is a Warehouse Management Systems Architect focused on enterprise integrations, IBM i modernization, and scalable backend systems.
Top comments (0)