# 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] [--extra 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`). - `--extra path`: extra C file linked in `m2` mode (after the shim; e.g. a tool's own `m2make_os.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). Modules the session needs but that are not named on the command line are resolved from the `-I` directories (so `-I runtime/syslib` finds `SysShim`/`FileIO`): the same shape the compiler's own `bootstrap/fixpoint.sh` uses. `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 # -> b_v3/M2make (bootstraps, then dogfoods) ./tests/run_tests.sh ``` `build_m2.sh` builds M2make with the V3 compiler. If a previous `b_v3/M2make` exists it drives the build itself (`M2make -c m2 --extra m2make_os.c -I runtime/syslib …`); on the first run (no driver yet) the session is spelled out by hand. Re-running it is therefore a small self-host check for the tool. ## Source conventions (both compilers must accept the code) `M2make.mod` targets the intersection of GNU Modula-2 and V3. The list below was re-probed against the current compilers; several old restrictions are gone (both V3 and gm2 now accept them): - No `_` in identifiers (still required by V3's classic lexer); 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). - `AND`/`OR` **short-circuit** in both compilers now, so ordinary index guards (`(i <= HIGH(s)) AND (s[i] # …)`) are safe. (The helpers still compute `Len` up front where that reads more clearly.) - Flat 1D arrays are used throughout; V3 now handles nested arrays and nested-element open-array actuals, so this is a style choice rather than a restriction. - `LONGINT` results from calls compile, but `VAL(INTEGER, longval)` (64→32) is still rejected by V3, so `LONGINT` crosses the `M2makeOS` boundary only as a `VAR` out-parameter; negative `INTEGER` printing is still avoided. - 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.