forgezero
- title
- forgezero
- type
- toolbox
- summary
- Makefile-free build tool wrapping NASM/GAS/FASM/GCC behind one strict-by-default command
- tags
- go, assembly, build-tool, nasm, gas, fasm, c, watchlist
- language
- Go
- license
- MIT
- created
- 2026-05-19
- updated
- 2026-07-22
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 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 _starts" 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
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 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 โ Go, MIT, 5 stars, v1.9.0 (2026-05-18).