# forgezero

Single-binary build tool for assembly and C/C++ that wraps NASM, GAS, FASM, GCC, Clang, and LD behind one `fz` command. Drops Makefiles entirely: point it at a file or directory, it picks the right backend by extension and links a binary. The target audience is people writing `.asm` and bare-metal C — the kernel/bootloader/firmware crowd that doesn't want to hand-wire `nasm -felf64` + `ld -T linker.ld` every iteration.

Tracked here because the defaults are unusually opinionated for a build tool and the v1.9 feature set is broader than the star count suggests. On the [[toolbox/watchlist]] — see "Status" below.

## Design choices worth noting

**Strict-by-default C.** Every `.c` file gets `-Wall -Wextra -Werror -Wpedantic -Wshadow -Wconversion` with no escape hatch. Same flags for C++ (`-Wpedantic` applies to ISO C++ too). Warnings stop the build. No `-Wno-anything`.

**Sanitizers on by default.** `-fsanitize=address,undefined` for every C/C++ build unless `-sanitize=false`. `-strict` adds `-fsanitize-address-use-after-return=always` (Clang-only) and `-fsanitize-address-use-after-scope`. Most build tools leave sanitizers off — this one flips it, which is a strong correctness bet but bad for release builds (you have to remember to disable).

**Three linking modes.** `-mode auto` tries `gcc` → `gcc -no-pie` → `ld` in sequence. `-mode c` forces gcc/clang (for libc programs). `-mode raw` invokes `ld` directly with no C runtime — the kernel/bootloader path, paired with `-T <script>` and `-Ttext <addr>` to control layout. Sanitizer flag conflicts mean `-type static` doesn't compose with `-mode raw`.

**Pre-link symbol check.** Before linking, `fz` runs `nm` (fallback: `objdump`, `readelf`) across every object and refuses to link if any global symbol is defined twice. Catches the classic "two `_start`s" or duplicate-function case before the linker emits its own cryptic error. Disable with `-no-symbol-check` if you're relying on weak symbols.

**Build cache keyed on (source SHA-256, debug, mode, target).** Stored in `.fz_cache/`. The `target` component means changing `-target arm-linux-gnueabihf` properly invalidates the cache instead of reusing native objects.

**Multi-level config merging.** `/etc/fz/fz.yaml` (system) → `~/.config/fz/fz.yaml` (user) → `.fz.yaml` (project) → CLI flags. Each layer overrides the previous. The system-level layer is the unusual one — most tools stop at user-level.

**Object-name collision avoidance.** `src/sub/hello.asm` becomes `src_sub_hello_asm.o` instead of `hello.o`, so multi-directory projects with same-base-name files (e.g. several `utils.c`) compile cleanly. Was a v1.8 fix after the obvious bug landed.

## CLI surface

```bash
fz -asm hello.asm                          # single file
fz -cc main.cpp                            # C or C++
fz -dir ./src                              # recursive scan + link
fz -dir ./src -type static -lib mylib      # produces libmylib.a
fz -cc main.c -target arm-linux-gnueabihf  # cross-compile
fz -compile-commands                       # emit compile_commands.json for clangd
fz -dir ./src -watch                       # fsnotify, 500ms debounce
fz -dir ./src -json                        # CI-shaped JSON report
fz -shell                                  # REPL
fz -init                                   # scaffolds .fz.yaml, .fzignore, README.md
```

`-j N` for parallel compile, `-j 0` for ncores. Cross-compile resolves prefixed tools (`<triple>-gcc`, `<triple>-g++`, `<triple>-ar`) on `PATH` and exits with code 2 if missing.

Supported extensions: `.asm` (NASM), `.s` / `.S` (GAS, the latter C-preprocessed), `.fasm` (separate flatassembler.net install), `.c`, `.cpp` / `.cc` / `.cxx`. Everything else silently skipped.

## What's interesting vs. what's not

The genuinely-new pieces are the strict-warnings-and-sanitizers-by-default posture and the symbol-pre-check before linking. Everything else (build cache, watch mode, JSON output, compile_commands generation, REPL, parallel builds) is reasonable but well-trodden — the value is bundling them with assembler support, where most modern build tooling assumes C/C++/Rust.

The other way to get rid of the Makefile is to not write a build tool at all. [[ultimate-gojust]] covers the same ground for Go — build with version injection, cross-compile, test, lint, watch, migrations — as a single Justfile you copy into the project, with nothing to install beyond `just`. The tradeoff is the usual one: `fz` can enforce its defaults because it owns the compiler invocation, while a Justfile is text anyone can edit past.

The cross-compile story is GNU-triple-prefix detection, not a clang `-target` invocation. So `riscv64-linux-gnu-gcc` must already be on PATH; `fz` doesn't ship a toolchain. That makes it thin glue over distro packaging, which is fine but means cross-compilation breaks the moment your distro lacks the package.

## Status

Two-month-old repo (created 2026-03-15), single author (alexvoste), 5 stars, MIT, repo just transferred to `forgezero-cli` org. Despite the v1.9.0 tag, this is firmly pre-adoption. Aggressive release cadence (1.5 → 1.9 in two months) suggests one person iterating fast, not a stable surface.

macOS support is "in progress." Windows support is "experimental" and requires MSYS2 + manual NASM install + Clang gymnastics for sanitizers. So the real target is Linux today.

On the [[toolbox/watchlist]] — re-check 2026-08-19. Want to see: a second contributor, macOS/Windows reaching parity, the strict-defaults / no-escape-hatch stance surviving real user pushback, and whether the project finds an audience beyond the "I write OS kernels for fun" niche it currently caters to.

## Repo

[github.com/forgezero-cli/ForgeZero](https://github.com/forgezero-cli/ForgeZero) — Go, MIT, 5 stars, v1.9.0 (2026-05-18).
