Add Nix flake (cross-platform dev shell), CI, and macOS support#14
Conversation
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.
|
Thanks for the PR. Let me preface this with a few important points:
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. |
|
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 Without all that extra, One open question I'd like your call on: With everything else reverted, Regards, |
|
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. |
|
@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 |
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.
09383be to
15e5496
Compare
|
Verifying target files... 🎉 |
ser-pounce
left a comment
There was a problem hiding this comment.
@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.
| }; | ||
|
|
||
| oldGccArchives = { | ||
| "2.7.2-cdk" = oldGccRelease "gcc-2.7.2-cdk-macos.tar.gz" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Is this addressed? It still seems like it's downloading the macos tarballs for all platforms including linux.
Co-authored-by: ser-pounce <ser-pounce@users.noreply.github.com>
|
Thanks, I pushed the latest changes on the PR branch, including the 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
|
Pushed a small format fix: re-ran Also a python.mk fix: the venv stamp target now uses a separate stamp file rather than touching And the CI now also builds the dev shell on |
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.
ser-pounce
left a comment
There was a problem hiding this comment.
Thanks, just a couple more things including a reopened comment about retrieving old-gcc tarballs.
| }; | ||
|
|
||
| oldGccArchives = { | ||
| "2.7.2-cdk" = oldGccRelease "gcc-2.7.2-cdk-macos.tar.gz" |
There was a problem hiding this comment.
Is this addressed? It still seems like it's downloading the macos tarballs for all platforms including linux.
ser-pounce
left a comment
There was a problem hiding this comment.
Will wait for cachix setup before merging
git blame is enough to find the commit that introduced any line; the rationale is in the commit messages and PR #14 body.
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(ordirenv allow) is the only setup step needed;make checkpasses on aarch64-darwin / x86_64-darwin / x86_64-linux. The legacy Ubuntuaptpath 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_INTfix (decompals/old-gcc#35).Commits
7 small, self-contained commits — happy to split into multiple PRs if that's easier to review:
flake.nix,flake.lock,.envrc,.gitignore(.direnv)$(ARCH)cpp(which is what produces matching codegen); fix?=→=to defeat GNU make's builtinCPP = $(CC) -E; keep-D_LANGUAGE_C(real bug fix vs the gcc-driver-injected define)requirements.txt— addresses your earlier comment on macOS build support via Nix flake (with Docker for old-gcc) #9MAINMENU.PRG/C48.c:444(independent fix)ci.yml(nix-build PR gate) +cache-warm.yml(manual cachix push)CI
nix-buildruns on every push and PR (ubuntu-latest). Validates the flake evaluates, builds the dev shell, assertsmipsel-linux-gnu-cppis gcc 12.x (newer cpp emits subtly different preprocessed output that breaks matching codegen — verified empirically), runs format check.cache-warmisworkflow_dispatch+ on push to main whenflake.{nix,lock}changes. Builds the dev shell onx86_64-linuxandaarch64-darwinand pushes torood-reverse.cachix.org.make checkitself 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 intonixConfig.extra-trusted-public-keysso contributors get pulls for free regardless of who controls write access. For thecache-warmworkflow's push to actually publish, the repo needsCACHIX_AUTH_TOKEN. Three options, your call: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)nix develop -c make -j check