# M2make Build-order front-end for the M2 (V3) and GNU Modula-2 compilers. Given a main file (`M2make main.mod`), it follows `IMPORT`/`FROM` imports transitively, topologically sorts the modules, skips the build when the output is newer than every source, and otherwise invokes the selected compiler. Exit codes: 0 success (or nothing to do), 1 build failure (incl. import cycles), 2 usage error. ```sh M2make [-n] [-v] [-c m2|gm2] [-I dir] [-o out] [--m2bin path] [--shim path] main.mod ``` - `-n`: dry run (print the build order and the command only). - `-v`: verbose (external modules, build order). - `-c m2|gm2`: compiler to drive (default `m2`). - `-I dir`: extra module search directory (repeatable; the main file's own directory is always searched). - `-o out`: output binary name (default: the program module name as declared in the main file). - `--m2bin path`: M2 driver binary (default `M2`). - `--shim path`: C runtime shim for `m2` mode (default `shim.c`). `m2` mode runs one session — ` ` then `qbe` then `cc .s -o -lm` — and gates on M2's `Parsed correctly` verdict (M2 exits 0 even on failure). `gm2` mode recompiles only stale units (`gm2 -fiso -c`, objects land in the cwd as `.o`) and links the main file with the objects in topological order. ## Building M2make itself The portable core is `M2make.mod` + `M2makeOS.def` (plain ISO Modula-2 importing only `FileIO` and `M2makeOS`). Everything non-portable lives in the per-compiler layer: - `m2make_os.c`: `stat`/`system`/`exit`/text-input/`/proc` argv helpers, with descriptor (`_d`, V3) and NUL-string (`_s`, gm2) variants. - gm2 side: `M2makeC.def` (`FOR "C"` binding) + `M2makeOS_gm2.mod`. - V3 side: `M2makeOS_v3.mod` (`EXTERNAL` binding). ```sh ./build_gm2.sh # gm2 -fiso -> b_gm2/M2make ./build_m2.sh # M2 session + qbe + cc -> b_v3/M2make ./tests/run_tests.sh ``` ## Source conventions (both compilers must accept the code) `M2make.mod` is restricted to the intersection the two compilers actually implement (all learned the hard way, via probes): - No `_` in identifiers, no `0C` (use `CHR(0)`), empty parameter lists need a space (`PROCEDURE P () : T`). - Declaration before use (V3 is single-pass; gm2 tolerates forward references). - All strings NUL-terminated; own `Len/Copy/Cmp/Cat` on top of indexed access (the two `FileIO` string layers differ). - Flat 1D arrays only: V3 rejects `a[i][j]` loads (233) and miscompiles nested-array elements passed as open-array actuals (double load → segfault). - Never rely on `AND`/`OR` short-circuit: V3 evaluates both sides and traps out-of-range indexing, so guards must dominate accesses structurally. - `LONGINT` only via `VAR` out-parameters (V3 rejects `LONGINT` function results in calls); never print negative `INTEGER`s. - No `ExitCode` global (gm2 has none): all exits via `M2makeOS.ExitNow`. No `SYSTEM`/`Storage` in portable code. ## Limitations - File names must match module names (exact or lowercase) with a `.def`/`.mod` extension; unknown modules are treated as external (system) libraries and skipped. - One main file per invocation; no spaces in paths; physical lines past ~2047 characters are split across reads. - `m2` mode rebuilds the whole session image when any source is stale (M2's session model); per-file incrementality is a `gm2` -mode feature. - `gm2 -c` drops `.o` in the cwd: two same-named modules in different directories collide.