Skip to content

build: Makefile wrappers for macOS/Linux/Windows/Android/iOS, bootstrap, opt-in Xcode download, BOM check and docs - #1482

Draft
parasew wants to merge 19 commits into
cypherstack:stagingfrom
parasew:feat/makefile-macos-build
Draft

parasew wants to merge 19 commits into
cypherstack:stagingfrom
parasew:feat/makefile-macos-build

Conversation

@parasew

@parasew parasew commented Nov 1, 2026 •

Copy link
Copy Markdown

Summary

Developer-experience tooling for building Stack Wallet from a checkout with make, rebased onto current staging (d6ca375, 19 commits, 28 files). It replaces the old draft #1336 and drops what was reviewed negatively there (no flutter create in the build, no patch scripts, no configure_* changes, no template pbxproj edits). Every build-* target runs the same steps as the matching CI job.

Nothing changes in app code or in what the existing CI jobs build; the new workflow is manual only. The only edits to existing files are small: scripts/prebuild.sh, scripts/app_config/templates/isar_build.sh, scripts/linux/*, README.md, .gitignore, docs/building.md.

What it adds

  • make build-macos: runs the same steps as the CI job (scripts/build_app.sh -p macos -a stack_wallet -s [-d], flutter pub get, scripts/prebuild.sh, flutter build macos --release). PREBUILTS=1 (default) passes -d, PREBUILTS=0 compiles the native assets from source. Output is the same app as an unmodified checkout: Stack Wallet.app, bundle ID com.cypherstack.stack_wallet, 319 MB.
  • make build-linux: same flags as the CI job (-d by default, SYSTEM_DEPS=1 adds -s for the system libsecret/jsoncpp packages).
  • Toolchain setup: make bootstrap-macos (Homebrew; pins Flutter 3.47.4, Rust 1.99.1 via rustup, cbindgen, ...) or nix develop / direnv (flake.nix, flake.lock, .envrc). make check-reqs, check-reqs-macos, check-sdk-macos, diagnose-env-macos.
  • Xcode: make bootstrap-xcode only checks/selects an installed Xcode. make bootstrap-xcode-download is opt-in and downloads a .xip from archive.org; it checks the SHA-1 listed by archive.org and Apple's signature (pkgutil --check-signature) and aborts if either fails, strips the quarantine attribute, never runs in CI, and accepts the license / runs first-launch only with -y. Default Xcode 27.1; dnix pins 27.0 for Apple source builds and lock verification, use XCODE_VERSION=27.0 for that.
  • make build-windows (Git Bash; same steps as the build-windows job, WINDOWS_COPY_ASSETS=1 for runners without symlinks), make build-android (CI steps; creates a throw-away debug key via android-dev-key when android/key.properties is missing, never for releases), make build-ios (flutter build ios --release --no-codesign).
  • make clean-app (removes build/ and Pods but keeps the native-asset outputs, next build 53 s instead of 494 s) and make clean-native.
  • stack-bom.toml + tool/verify_bom.dart + make verify-bom: checks Flutter/Dart/Rust/Go/Xcode and the pinned native plugins against one small file (no packages needed, --update regenerates the plugin list from pubspec.lock).
  • .github/workflows/make-build.yaml: manual only (workflow_dispatch), runs the make targets on Windows, macOS, Linux (plus verify-bom) and iOS runners. It does not change the existing workflows.
  • make sccache-setup (opt-in; writes ~/.cargo/config.toml only if absent) and make cache-stats.
  • bm/ benchmark scripts (bm/run_all.sh: cold/warm, prebuilt/from source, sccache on/off, sccache hits recorded) and the MWC FFI integration tests (make test-mwc).
  • docs/building.md: new "Building with make" section.

Small fixes in existing scripts

  • isar_build.sh looked for isar_core_ffi under a hard-coded $HOME/.pub-cache; it now uses ${PUB_CACHE:-$HOME/.pub-cache}.
  • prebuild.sh uses #!/usr/bin/env bash (there is no /bin/bash on NixOS). build_app.sh is unchanged; the Makefile runs it from scripts/ like the CI jobs do.

Verification (one machine each, one run each)

What Result
macOS 27.2, Xcode 27.1, Apple Silicon: make build-macos, clean checkout OK, 946 s cold, Stack Wallet.app 319.0 MB, bundle ID unchanged
same, PREBUILTS=0 (all native assets from source) OK, 596 s
same, inside nix develop (Flutter 3.47.4 from nixpkgs) OK, 261 s
NixOS x86_64: make build-linux (nix develop, Rust 1.99.1) OK, bundle/stack_wallet, 291 MB
Xcode 27.1 from archive.org (download only) 2.03 GB, SHA-1 matches, Apple signature valid; the sudo install step was not run
Ubuntu 24.04 container (apt packages of the CI Dockerfile, Rust 1.99.1, Go 1.24.13, Flutter 3.47.2): make build-linux, clean checkout OK, bundle 289 MB, about 23 min
GitHub-hosted runners in a fork (this branch plus a workflow that only calls the make targets): make build-macos on macos-latest, make build-linux on ubuntu-24.04 both OK, 319 MB app / Linux bundle built
Xcode installer into a user-writable directory (no sudo), using the verified 27.1 archive OK, xcodebuild -version = 27.1 (27A9275); xcode-select/license/first-launch (sudo) not run
scripts/install_macos_build_tools.sh on a Mac with Homebrew (temporary FLUTTER_DIR) OK after the fix in this PR (first run failed on an existing weasyprint link)
NixOS x86_64 (nix develop): make build-linux with the default -d (prebuilt native assets), fresh checkout OK, bundle built, 17 min
Ubuntu 24.04 container (apt packages of the CI Dockerfile): make build-linux SYSTEM_DEPS=1 (= -d -s, the CI flags) OK, bundle built, 16 min
Fresh macOS 27.2 VM (no Homebrew, no Xcode): download_xcode.sh -y (incl. xcode-select, license, first launch), make bootstrap-macos, make build-macos OK, about 25 min end to end, Stack Wallet.app 319.0 MB
dart format --set-exit-if-changed on the changed Dart files OK
All of the above combined (18 commits), GitHub runners in my fork, manual workflow: make build-windows (windows-2022), make build-macos (macos-latest), make verify-bom + make build-linux (ubuntu-24.04), make build-ios all four OK: Windows 25 min, macOS 21 min, Linux 16 min, iOS 14 min
macOS 27.2, make build-macos on the combined branch OK, 297 s, 319 MB
make verify-bom 0 failures; warns about Flutter 3.47.2 (recommended 3.47.4) and Xcode 27.2 (recommended 27.1). A wrong Rust version or plugin pin makes it exit 1
Android in a container from this repo's Dockerfile (--target android), before the last merge, same Makefile code OK with android-dev-key: APKs 183 / 219 / 235 MB (arm / arm64 / x86_64). Without a key it fails at :app:packageRelease
iOS on macOS 27.2 / Xcode 27.1 Runner.app 205.5 MB, 452 s, device build, no simulator
make clean-app then rebuild 53 s vs. 494 s after a full clean

An unmodified staging also builds on macOS 27.2 without any patches (about 12 min with -d, 13 min without), so none of the Xcode-27 / deployment-target workarounds of the older branches are needed.

Not tested: a physical fresh Mac (the VM test above is Apple Silicon only), a Windows developer machine (only the hosted runner), Android with a real release key / app bundle, iOS simulator and signing. The upstream Actions jobs have not run on this PR; the runner results above come from the manual workflow in my fork (the Linux runner needed a disk-space cleanup step, already in the workflow). The runner results above are from the first 18 commits; the 19th (review fixes: Windows asset copies, Nix bootstrap guard, Linux dependency build errors, rustup PATH, Xcode selection, slatepack test, bm/ scripts) was checked with stubbed fixtures and upstream's test / linux-release-bundle, not with full platform builds.

Things found on the way

  • NixOS: prost-build 0.7 (via mwc-libp2p-core) ships a generic-Linux protoc that NixOS cannot run, and PROTOC exported in the shell does not reach the hook builds (Flutter 3.47.x filters the environment). make build-linux therefore writes a git-ignored .cargo/config.toml with [env] PROTOC = "<protoc from PATH>" if none exists. The proper fix is in flutter_libmwc (let the hook hand cargo a protoc found on PATH); I tried that once on NixOS and it worked: https://mam.riat.foo/flutter_libmwc_use_path_protoc.patch
  • build_app.sh only regenerates pubspec.yaml from the template when the file is unchanged, so the -d choice of an earlier run sticks in a reused checkout; the Makefile deletes the generated pubspec.yaml first.
  • In nix develop, dart run flutter_launcher_icons failed because the Dart from the nixpkgs Flutter wrapper does not find the Flutter SDK; the flake now sets FLUTTER_ROOT.
  • nixpkgs has no Flutter 3.47.2 (3.47.0, then 3.47.4), so the Nix shell is on 3.47.4. hooks_runner is 1.5.0 in both.
  • Every macOS build prints invalid character in Bundle Identifier because of the underscore in com.cypherstack.stack_wallet. Not touched here, it would change the app identity.

Caching notes (see also bm/)

Hooks run with a filtered environment (Flutter 3.47.x, hooks_runner 1.5.0), so the old env-based approach no longer applies. Measured on macOS, from-source build of all native assets, one run each: 775 s without sccache, 665 s with sccache and an empty cache (-14%), 534 s with a warm cache (-31%). It works through a cargo config file (make sccache-setup). Prebuilts (-d) only cover tor/epic/mwc, about 12 vs. 13 min cold.

With the Flutter 3.50 beta (hooks_runner 1.6.4) a plain CARGO_BUILD_RUSTC_WRAPPER=sccache reaches the hooks without a config file. The project does not resolve on 3.50 at the moment (mockingjay needs analyzer 13-15, we pin ^10.2).

Suggestions

Raise Flutter to ^3.47.4 (CI flutter-version, docs, pubspec.template.yaml). Same hooks_runner as 3.47.2, nixpkgs only has 3.47.4, and I built with both. I did not touch the CI pins or the constraint in this PR.

Not in this PR (follow-ups)

The flutter_libmwc protoc change and a per-library dylib install name in the hooks (both become unnecessary if the four plugin pins move to their dnix commits, see my message), dnix for xelis/spark/mwebd, remaining caching experiments, and Flutter 3.50 (a migration of the code-generation stack). A decision is needed on how flake.nix relates to dnix's own Nix setup.

Supersedes #1336; that draft can be closed once this is reviewed.

Lunar Dev added 18 commits November 1, 2026 20:15
Adds a Makefile that wraps scripts/build_app.sh:
- make build-macos runs the same steps as CI (build_app.sh, pub get,
  prebuild.sh, flutter build macos). PREBUILTS=0 compiles native assets
  from source (the generated pubspec.yaml is removed first so an earlier
  -d run does not stick). Produces the same app as upstream
  (Stack Wallet.app, com.cypherstack.stack_wallet).
- make bootstrap-macos (Homebrew; pins Flutter 3.47.2), nix develop via
  flake.nix/.envrc (sets FLUTTER_ROOT so `dart run` works), make
  bootstrap-xcode, and an opt-in make bootstrap-xcode-download
  (archive.org; SHA-1 and Apple signature are both checked).
- make sccache-setup (opt-in): sccache for native-asset hook builds via
  ~/.cargo/config.toml.
- make build-linux; writes a git-ignored .cargo/config.toml with PROTOC so
  crates that ship a generic-Linux protoc build on NixOS.
- bm/ benchmark scripts and MWC integration tests.

Docs: new "Building with make" section in docs/building.md.

Verified (one machine each, one run):
- macOS 27.2 / Xcode 27.1: make build-macos from a clean checkout
  (946 s cold, 319.0 MB), PREBUILTS=0 (596 s), and inside nix develop
  (Flutter 3.47.4, 261 s).
- Linux: NixOS x86_64, nix develop, Rust 1.99.1, make build-linux
  produced build/linux/x64/release/bundle/stack_wallet (291 MB).
- Xcode 27.1 download from archive.org: SHA-1 and pkgutil signature
  check pass (install step not run).
Not tested: Ubuntu, Windows, Android, iOS, bootstrap-macos on a fresh Mac.
…set hooks

Measured on macOS with the unmodified upstream tree: a repeated build takes 33-40 s with an
unchanged environment, but 536 s after one extra directory in PATH and 156-164 s after adding a
NIX_* variable or restoring PATH, because hooks_runner reruns every hook when the environment it
hashes changes.
download_xcode.sh used sudo for rm/mv/xattr unconditionally. It now checks whether the parent of the target
directory is writable and only then uses sudo, so Xcode can be installed into ~/Applications without a password.
Verified on macOS 27.2: -d ~/Applications/Xcode_27.1_test.app installed Xcode 27.1 (27A9275), the extracted app
has no quarantine attribute, and free space dropped by about 4 GB; hash and signature checks passed before it.
The install-time prompt text now says about 20 GB free are needed.
…ptional

install_macos_build_tools.sh aborted at the first brew failure. On a machine where weasyprint was installed another way,
brew could not link it, set -e stopped the script, and the Flutter step was never reached. It also upgraded formulae that
were already installed (cmake, go), changing the build machine under the user.
- export HOMEBREW_NO_INSTALL_UPGRADE=1 for the required packages
- install pandoc/weasyprint/toilet/figlet (only used by the bm/ report rendering) in a separate, non-fatal step

Verified on macOS 27.2: first run failed as described; after the change the installer ended with rc=0, cloned Flutter 3.47.4
into a temporary FLUTTER_DIR, left the existing Flutter 3.47.2 untouched, and only appended the brew shellenv and Flutter PATH
lines to ~/.zshrc (restored afterwards).
…lock

make build-macos now runs ./build_app.sh from the scripts directory (as the CI jobs and the other targets do), so
scripts/build_app.sh stays exactly as in staging; the earlier path-resolution and variable-rename edits are reverted.
make build-linux no longer recreates lib/external_api_keys.dart: prebuild.sh already creates it, and the block never
ran in any of the Linux builds I did.

Verified on macOS 27.2: make build-macos OK, Stack Wallet.app with the unchanged bundle ID. The Linux target was only
checked with make -n after this change.
archive.org sometimes resets the connection (curl exit 56) a minute into the transfer, which aborted the script even
though the download can be resumed. curl now retries up to 8 times (--retry-all-errors) and resumes with -C -.

Seen in a fresh macOS 27.2 VM; the retry itself is exercised in the next VM run.
…e, docs

make build-linux now passes -d (PREBUILTS=1, the default, like make build-macos and the CI job) and -s with SYSTEM_DEPS=1.
With -s the Makefile sets only USE_SYSTEM_SECURE_STORAGE_DEPS=1 for the app build; the local libsecret pkg-config override
is kept for the default path (it is needed where libsecret is built from source, for example on NixOS).
download_xcode.sh accepts XCODE_XIP_URL to download from another mirror; the SHA-1 and the Apple signature are still checked.
docs: a second Xcode via DEVELOPER_DIR, and what reaches the hooks for sccache (exported RUSTC_WRAPPER does not, the cargo
config file does).

Verified: NixOS (nix develop) make build-linux with -d builds the bundle; Ubuntu 24.04 container make build-linux
SYSTEM_DEPS=1 (-d -s, the CI flags) builds the bundle; make -n and bash -n pass for all targets and scripts.
… bm/bench.sh

clean-app removes build/ and the Pods but keeps .dart_tool/hooks_runner, so the next build does not rebuild the
native assets; clean-native removes only the native-asset outputs. bm/bench.sh zeroes the sccache statistics
before the build and stores 'sccache hits/executed requests' in the comment column of the CSV.
stack-bom.toml records the expected toolchain (Flutter, Dart, Rust, Go, Xcode)
and the git revisions of the pinned native plugins. make verify-bom
(dart tool/verify_bom.dart, no packages needed) checks the local machine and
pubspec.lock against it: a mismatch in Rust, Dart, minimum Flutter/Go or a
plugin pin fails, a different recommended Flutter or Xcode only warns.
dart tool/verify_bom.dart --update regenerates the [plugins] section from
pubspec.lock.

Verified on macOS 27.2: passes with 1 warning (Flutter 3.47.2 vs. recommended
3.47.4); a wrong Rust version and a wrong plugin pin both exit 1.
…targets

make build-windows wraps the steps of the build-windows CI job (prebuild.sh, build_app.sh -p windows -d -s,
pub get, coinlib:build_windows, flutter build windows). WINDOWS_COPY_ASSETS=1 replaces the asset links with
copies for runners that cannot create symlinks.
.github/workflows/make-build.yaml runs make build-windows and make build-macos on hosted runners
(manual dispatch or pushes to ci/make-* branches).
…ing key exists

The release build fails at packageRelease without android/key.properties (SigningConfig release is missing
storeFile). android-dev-key creates a debug keystore and key.properties for local builds only; both files are
git-ignored and the APKs are not for distribution.
@parasew parasew changed the title build: Makefile wrapper, macOS/Linux bootstrap, opt-in Xcode download and docs build: Makefile wrappers for macOS/Linux/Windows/Android/iOS, bootstrap, opt-in Xcode download, BOM check and docs Nov 2, 2026
…deps and bm

- build-windows: with WINDOWS_COPY_ASSETS=1 remove the asset copies of an earlier run before
  build_app.sh runs link_assets.sh, whose `rm -f` cannot delete real directories (second run failed)
- bootstrap-macos: skip the Homebrew installer in a Nix shell with one conditional recipe instead of
  `exit 0`, which only ended that line's shell; failures of the Xcode step now propagate
- build_secure_storage_deps.sh: stop when cmake, make, meson or ninja fails, before writing the
  pkg-config file; reconfigure an existing meson build directory instead of failing on it
- install_macos_build_tools.sh: put rustup on PATH after `brew install` (resolved with
  `brew --prefix rustup`), not only if its directory existed before
- install_xcode.sh: keep any valid selected Xcode (Xcode_27.1.app, ~/Applications, ...) and only
  pick one from /Applications when the selection is missing or the Command Line Tools
- mwc_ffi_test_service: the invalid-slatepack assertion no longer sits inside its own catch
- bm/compare.py: pair measurements by label and host instead of the first row of each file
- bm/bench.sh: record failed builds and return make's exit status
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.

1 participant