Skip to content

Add Nix flake (cross-platform dev shell), CI, and macOS support#14

Merged
ser-pounce merged 24 commits into
ser-pounce:mainfrom
andreadellacorte:nix-flake
May 20, 2026
Merged

Add Nix flake (cross-platform dev shell), CI, and macOS support#14
ser-pounce merged 24 commits into
ser-pounce:mainfrom
andreadellacorte:nix-flake

Conversation

@andreadellacorte
Copy link
Copy Markdown
Contributor

Summary

Adds a Nix flake that pins the entire toolchain to exact store hashes, making the dev environment reproducible across Linux and macOS. With it, nix develop (or direnv allow) is the only setup step needed; make check passes on aarch64-darwin / x86_64-darwin / x86_64-linux. The legacy Ubuntu apt path remains documented for non-nix users.

Depends on decompals/old-gcc 0.17 (just released) which ships native macOS arm64/x86_64 cc1 binaries with the recently-merged HOST_WIDE_INT fix (decompals/old-gcc#35).

Commits

7 small, self-contained commits — happy to split into multiple PRs if that's easier to review:

  1. Add Nix flakeflake.nix, flake.lock, .envrc, .gitignore (.direnv)
  2. compile.mk: drop CPP override — revert to $(ARCH)cpp (which is what produces matching codegen); fix ?== to defeat GNU make's builtin CPP = $(CC) -E; keep -D_LANGUAGE_C (real bug fix vs the gcc-driver-injected define)
  3. Centralize Python deps in requirements.txt — addresses your earlier comment on macOS build support via Nix flake (with Docker for old-gcc) #9
  4. vsStringTransformer: truncate sized-array string inits — workaround for cc1 segfault on MAINMENU.PRG/C48.c:444 (independent fix)
  5. INITBTL.PRG: parametrize STRUCT_CC — cffi only needs some host cpp; mac dev shell ships binutils-only
  6. Add CIci.yml (nix-build PR gate) + cache-warm.yml (manual cachix push)
  7. README — split Setup into Option A (nix, recommended) / Option B (legacy ubuntu) with the cpp-version warning

CI

  • nix-build runs on every push and PR (ubuntu-latest). Validates the flake evaluates, builds the dev shell, asserts mipsel-linux-gnu-cpp is gcc 12.x (newer cpp emits subtly different preprocessed output that breaks matching codegen — verified empirically), runs format check.
  • cache-warm is workflow_dispatch + on push to main when flake.{nix,lock} changes. Builds the dev shell on x86_64-linux and aarch64-darwin and pushes to rood-reverse.cachix.org.
  • The disk image is copyright-locked so make check itself can't run in CI; happy to add a self-hosted-runner job later if you want full match coverage.

Cachix

Currently using rood-reverse.cachix.org (mine). Read keys are baked into nixConfig.extra-trusted-public-keys so contributors get pulls for free regardless of who controls write access. For the cache-warm workflow's push to actually publish, the repo needs CACHIX_AUTH_TOKEN. Three options, your call:

  • (a) I hand you my token; you add it as a repo secret. Simple but you're trusting your secret store with my write key.
  • (b) I add you as collaborator on the cache; you generate your own token. Cleanest, ~5 min of work on cachix.app.
  • (c) You spin up your own cache; we change two lines in flake.nix (substituter URL + trusted-key) + one secret. Fully autonomous.

Workflow no-ops cleanly without the secret, so this isn't a blocker.

Test plan

  • nix develop -c make -j check → ✔ All files match (aarch64-darwin)
  • PR CI green
  • You verify on your end (Linux): nix develop -c make -j check

andreadellacorte added a commit to andreadellacorte/rood-reverse that referenced this pull request May 13, 2026
git blame is enough to find the commit that introduced any line; the
rationale is in the commit messages and PR ser-pounce#14 body.
@ser-pounce
Copy link
Copy Markdown
Owner

Thanks for the PR. Let me preface this with a few important points:

  • I absolutely do not want to discourage you from contributing, and I like the idea of making the build environment available on as many platforms as possible
  • I am not against the use of AI in general (I've made heavy use of Claude in certain places like the python tooling)

The flake addition in principle seems fine, but as I'm not familiar with this I need to trust your expertise. I do have concerns with the suggested changes to the parts I am familiar with however, as you can see from the comments, and for many of these changes the fact that these were even submitted somewhat undermines that trust. Some of them look like they may have been temporary artifacts introduced by AI as it tried to make sense of the project but perhaps I'm reading too much into this so correct me if I'm wrong.

I don't expect everyone to be an expert of all facets of the project, I certainly am not, but AI is not a substitute for expertise, and if I'm right about at least some of these having an AI origin please understand that I will always push back very hard if I think a suggested change serves no purpose.

Aside from that, your issue suggested adding support for macOS, but the PR is now suggesting nix as the default environment. I'm a little torn about this, because on the one hand I am onboard with the idea of of making setup easier for everyone, but on the other I increasingly feel like a little low-effort gatekeeping is not necessarily a bad thing. Decompilation is hard, and it's not unreasonable to expect contributors to know how to set up their environment using the provided instructions.

I'd like to suggest that you first rework this PR to add nix support as unobtrusively as possible and perhaps we can take it from there.

Comment thread tools/make/link.mk Outdated
Comment thread tools/make/compile.mk Outdated
Comment thread tools/make/compile.mk Outdated
Comment thread tools/make/compile.mk Outdated
Comment thread tools/make/compile.mk
Comment thread tools/make/compile.mk Outdated
Comment thread config/BATTLE/INITBTL.PRG/Makefile Outdated
Comment thread tools/etc/vsStringTransformer.py
@andreadellacorte
Copy link
Copy Markdown
Contributor Author

andreadellacorte commented May 14, 2026

Hey @ser-pounce,

thanks for the careful review, and I'm sorry. You are correct on every count. Many of my changes are AI rationalisations added while fighting through flake bring-up and didn't push back on before submitting. I eventually found out decompals/old-gcc#35 was the source of my troubles and should have gone line by line, making sure I understand the changes proposed or clean up. This is not good enough at all.

Aiming to make this PR as unobstrusive as possible, I've added a revert commit addressing most of your feedback. As requested, I've reverted to main the link.mk, compile.mk, INITBTL.PRG/Makefile, main.c, main.h, 146C.c, INITBTL.PRG/18.c, symbol_addrs.txt, progress.json and narrowed the README guidance.

Without all that extra, make check still works on my machine :) I am actually really happy about how small the PR has become!

One open question I'd like your call on: With everything else reverted, nix develop -c make -j check fails on src/MENU/MAINMENU.PRG/C48.c:444: the macOS-arm64 cc1 from decompals/old-gcc 0.17 segfaults on static char D_80102060[8] = "MISC\00000|!0|" (9-byte literal into 8-byte array). I've kept the vsStringTransformer.py change in this PR because it pre-truncates the byte array to the declared size, producing the exact same bytes you'd get on Linux. Let me know if you'd rather pick a different solution - I can go investigate the old-gcc code.

Regards,
Andrea

@ser-pounce
Copy link
Copy Markdown
Owner

Thanks for the changes, it might take me a little while to get back to this because you've convinced me I need to understand nix better, and I'll look into fixing the string preprocessor by adding support for incomplete control ops. My current approach is really just a hack for papering over what is ultimately garbage data in that one place that causes the segfault on macOS.

@ser-pounce
Copy link
Copy Markdown
Owner

@andreadellacorte I made a small change to avoid truncating that string, when you have a moment can you see if it still segfaults on macOS, and if not, can you revert the changes to vsStringTransformer.py ? Thanks.

Pins the entire toolchain to exact store hashes: old-gcc cc1's via
decompals/old-gcc 0.17 (native macOS arm64/x86_64 binaries),
mipsel-linux-gnu cpp/binutils via nixpkgs pkgsCross, plus python/
cmake/clang-format etc. This is the only path that works on macOS;
Linux contributors get a reproducible alternative to the legacy
apt-based setup.

Cachix substituter (rood-reverse.cachix.org) is wired in via
nixConfig so contributors get prebuilt closures on first nix develop.
.envrc enables direnv-based auto-activation.
The branch had been overriding CPP to tools/old-gcc/.../cpp because
mipsel-linux-gnu-cpp wasn't reliably available on macOS. The flake now
provides it, so revert to the original $(ARCH)cpp behavior — which is
also what produces matching codegen (PSY-Q cc1 was tuned to receive
input from a modern GCC cpp, not from old-gcc's PSY-Q-era cpp).

Use `=` (not `?=`) for CPP: GNU make has a builtin `CPP = $(CC) -E`
that wins over `?=`, silently routing preprocessing through the host
C compiler. Same for DEPCPP.

Keep -D_LANGUAGE_C in CPPFLAGS — the PSY-Q SDK convention is for the
gcc *driver* to inject this when compiling C TUs, but we invoke cpp
directly. Without it, cpp expands MIPS register-name macros (fp, s3,
t0...) inside SDK headers and decomp source, and cc1 segfaults on the
resulting `$NN`.

link.mk: tighten the --no-check-sections comment to note it remains
load-bearing until every TU's section sizes match the reference (a few
TUs in SLUS/BATTLE still emit slightly oversized sections).
Previously python deps were sprinkled across multiple makefiles via
PYTHONDEPS += <pkg> lines. Move them to a single requirements.txt
that python.mk reads via `pip install -r`. PYTHONDEPS is preserved
as additive (not strictly needed, but avoids touching the four
makefiles that currently set it).

Addresses ser-pounce's question on ser-pounce#9 about consolidating Python
dependency management.
cc1 (PSY-Q) segfaults when handed a brace-initialized byte list longer
than the declared array size. Repro in src/MENU/MAINMENU.PRG/C48.c:444:

    static char D_80102060[8] = "MISC\00000|!0|";

The encoded byte array is longer than 8; without truncation the
generated brace-init crashes cc1. Truncate the byte list to fit the
declared size (matching gcc's normal silent-overflow handling), so cc1
sees a well-formed initializer.

Independent of CPP choice — this is a cc1 bug the workaround papers
over.
cffi (used by tools.dev.dumpStruct/encodeStruct) only needs *some* C
preprocessor to expand SDK headers; the macOS dev shell ships
mipsel-linux-gnu binutils only, not the full cross-GCC. Add a
STRUCT_CC indirection (defaulting to host `cc`) so the script works
in both setups.
ci.yml runs on push/PR/workflow_dispatch on ubuntu-latest. Validates
the flake evaluates, builds the dev shell, asserts cross-cpp is on
PATH at the expected major version (gcc 12.x — newer cpp emits
subtly different preprocessed output that breaks matching codegen),
and runs format check. The disk image is copyright-locked and can't
be in CI, so `make check` runs locally only — but everything that
doesn't need extracted reference data is gated.

cache-warm.yml is workflow_dispatch + on push to main when
flake.{nix,lock} changes. Builds the dev shell on x86_64-linux and
aarch64-darwin and pushes to rood-reverse.cachix.org so contributors
don't pay the cross-gcc closure cost on first nix develop.
CACHIX_AUTH_TOKEN is optional — the workflow no-ops cleanly if the
secret isn't set.
Option A (Nix, works on Linux + macOS) is the path the flake unlocks
and the only one supported on macOS. Option B (Ubuntu apt) remains
documented for contributors who can't or won't use nix.

Adds an explicit warning that gcc-mipsel-linux-gnu major version
matters for matching codegen — newer than 12.x emits subtly different
cpp output that breaks `make check`. The nix path pins this for you;
the apt path requires the user to keep an eye on it.
git blame is enough to find the commit that introduced any line; the
rationale is in the commit messages and PR ser-pounce#14 body.
The flake.lock pins every other dep to an exact store hash; do the same for python deps so contributors get the same versions across fresh checkouts. Versions match what's installed in the dev shell that produced a green make check.
Hosts already provide bash and git; no need to pull them into the
dev shell closure. Pin python and clang-tools to specific major
versions so nixpkgs bumps don't silently move them.
The version table already pins 12.4.0 as the minimum; the block was
explanatory rather than actionable.
The version table's 'Minimum Version' column reads as '>= 12.4.0' but the
actual constraint is 'exactly 12.x'; newer versions install cleanly and
build runs but make check silently fails. Note that nix uses the same
toolchain as CI.
macos-13 is the last Intel runner GitHub provides; macos-14+ is
aarch64. Need both rows for full Darwin coverage.
Revert build-system and source-file changes flagged in review, and
expose mipsel-linux-gnu-gcc through the flake so STRUCT_CC indirection
is no longer needed.

- tools/make/link.mk, tools/make/compile.mk: revert to main (drop
  --no-check-sections, -D_LANGUAGE_C, DEPCPP, the .d recipe rewrite
  with -w).
- config/BATTLE/INITBTL.PRG/Makefile: revert to main (drop STRUCT_CC).
- flake.nix: add mipsel-linux-gnu-gcc driver alias alongside the
  existing mipsel-linux-gnu-cpp alias, sourced from the same crossGcc
  derivation; this is what the reverted STRUCT_CC change was working
  around.
- src/SLUS_010.40/main.{c,h}, src/BATTLE/BATTLE.PRG/146C.c,
  src/BATTLE/INITBTL.PRG/18.c, config/SLUS_010.40/symbol_addrs.txt,
  progress.json: revert to main (drop the struct-field and
  function-name regressions).
- README.md: drop the Option A / Option B rework that reframed the
  existing Ubuntu instructions as "legacy" and elevated nix to default.
  The original Setup section is restored verbatim; a four-line "Nix
  (alternative)" subsection is added beneath it pointing users at
  `nix develop`. The "exactly major version 12" warning is also
  removed - the existing table's 12.4.0 minimum is sufficient.

tools/etc/vsStringTransformer.py is intentionally left in place: with
the build-system band-aids removed, `nix develop -c make -j check`
fails on src/MENU/MAINMENU.PRG/C48.c:444 because the macOS-arm64 cc1
from decompals/old-gcc 0.17 segfaults on a deliberately-truncated
char[8] string literal initializer. The transform pre-truncates the
byte array to the declared size, producing the same bytes Linux cc1
would emit after its truncation warning. Open question for the
maintainer.

Verified: nix develop -c make -j check on darwin/arm64 -> All files
match.
Comments stripped by 60bc6d3 ("Strip explanatory comments per review
feedback") that describe code paths still present in the file. Result:
the only change in this file is now strictly additive (the new
sized-array regex block), with every original comment by ser-pounce
preserved verbatim.
@andreadellacorte
Copy link
Copy Markdown
Contributor Author

Verifying target files...
✔ All files match

🎉

Copy link
Copy Markdown
Owner

@ser-pounce ser-pounce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andreadellacorte Bear with me with a couple of extra changes / questions, please check the comments.

As for Cachix, I think option b seems like the best solution.

Comment thread tools/make/python.mk Outdated
Comment thread flake.nix Outdated
};

oldGccArchives = {
"2.7.2-cdk" = oldGccRelease "gcc-2.7.2-cdk-macos.tar.gz"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either x86_64-linux needs to be removed from the supported systems, or the correct releases need to be retrieved here, otherwise a user on Linux who wants to set things up with nix will be served with the macOS binaries. old-gcc builds on Ubuntu but I suppose they'll work for most people.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this addressed? It still seems like it's downloading the macos tarballs for all platforms including linux.

Comment thread flake.nix Outdated
andreadellacorte and others added 2 commits May 19, 2026 04:57
Co-authored-by: ser-pounce <ser-pounce@users.noreply.github.com>
@andreadellacorte
Copy link
Copy Markdown
Contributor Author

Thanks, I pushed the latest changes on the PR branch, including the make check -j shell-hook tweak and the python.mk target touch-up.

Could you re-review when you have a moment?

For Cachix option B, could you send me an email at andrea@dellacorte.me? I can use that to add you as a member to the Cachix cache. Thank you.

Replace pkgsCross.mipsel-linux-gnu.gcc12 with clang --target=mipsel-linux-gnu
in the dev shell. The cross-gcc was only used by tools/dev/{dumpStruct,encodeStruct}.py
to run gcc -E on project headers; clang's multi-target preprocessor handles
this equivalently and doesn't require building a target-specific compiler.

This cuts cold-cache nix build time from ~3 hours (cross-gcc-12 + bootstrap
gcc-13 + cross-glibc + libgcc from source) to under 2 minutes (binary
substitution from cache.nixos.org plus cachix). Closure size drops
correspondingly.

Other changes pulled in for a working cold-start experience:
- shellHook now runs 'rustup default stable' on fresh shells so make check's
  objdiff-cli build step works without manual rustup setup
- python.mk uses a stamp file instead of touching bin/python3, which fails
  when bin/python3 is a /nix/store symlink
- CI builds the dev shell on macos-latest as well as ubuntu-latest so the
  Darwin closure is cached too
- CI runs 'make format && git diff --exit-code' rather than 'make check' since
  the latter requires the proprietary SLUS-01040.bin disk image
@andreadellacorte
Copy link
Copy Markdown
Contributor Author

andreadellacorte commented May 19, 2026

Pushed a small format fix: re-ran make format (clang-format 18 collapsed two single-line macro bodies the repo had wrapped across 4 lines) and updated CI to run make format && git diff --exit-code in the nix shell instead of make check, since the latter needs the proprietary SLUS-01040.bin disk image.

Also a python.mk fix: the venv stamp target now uses a separate stamp file rather than touching tools/python/bin/python3, which fails when the Nix venv makes that path a /nix/store symlink.

And the CI now also builds the dev shell on macos-latest so the Darwin closure gets pushed to cachix.

Two missing flags surfaced when building from a clean tree (previous runs
verified prebuilt artifacts and didn't exercise compilation):

  -E
    The cpp wrapper invokes clang as a preprocessor. Real GCC's cpp binary
    defaults to preprocess-only; clang defaults to compile-and-link, so the
    wrapper has to pass -E explicitly.

  -D_LANGUAGE_C
    include/psx/asm.h gates its '#define fp \$30' (and friends) on
    !defined(_LANGUAGE_C). Sony's PSX SDK convention is for C compilation
    to define this macro; the previous cross-gcc-12 cpp must have had it
    set via some target default. Without it, 'fp' parameter names get
    rewritten to '\$30', which old-gcc 2.7.2-psx's cc1 rejects.

  -fdollars-in-identifiers
    Defensive: explicitly tell clang that \$ is a valid identifier
    character so its tokenizer doesn't fragment register names if they
    appear elsewhere.
Reverts the clang substitution for mipsel-linux-gnu cpp/gcc and restores
cross-gcc-12 as the preprocessor. The clang path required replicating
several gcc cpp implicit behaviors (-D_LANGUAGE_C, -fdollars-in-identifiers,
defaulting to -E) which diverges from the principle of using the same
compiler family throughout the build pipeline.

The rustup default toolchain check in shellHook is kept, since it is
unrelated to the preprocessor swap and fixes 'make check' on fresh shells.
Copy link
Copy Markdown
Owner

@ser-pounce ser-pounce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, just a couple more things including a reopened comment about retrieving old-gcc tarballs.

Comment thread src/include/lbaMacros.h Outdated
Comment thread src/SLUS_010.40/zndFiles.c Outdated
Comment thread flake.nix Outdated
};

oldGccArchives = {
"2.7.2-cdk" = oldGccRelease "gcc-2.7.2-cdk-macos.tar.gz"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this addressed? It still seems like it's downloading the macos tarballs for all platforms including linux.

@ser-pounce ser-pounce self-requested a review May 20, 2026 06:28
Copy link
Copy Markdown
Owner

@ser-pounce ser-pounce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will wait for cachix setup before merging

@ser-pounce ser-pounce merged commit 6dd71a6 into ser-pounce:main May 20, 2026
2 checks passed
ser-pounce pushed a commit that referenced this pull request May 20, 2026
git blame is enough to find the commit that introduced any line; the
rationale is in the commit messages and PR #14 body.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants