fix(amm): make flake portable across macOS and Linux

This commit is contained in:
Ricardo Guilherme Schmidt 2026-07-17 09:26:13 -03:00
parent 06054770ef
commit 1692581f60
No known key found for this signature in database
GPG Key ID: 1396EA17DE132FFE
6 changed files with 803 additions and 518 deletions

173
apps/amm/MACOS.md Normal file
View File

@ -0,0 +1,173 @@
# AMM UI on macOS
The AMM flake exposes native packages for Apple Silicon and Intel macOS. Nix
provides the Rust, C++, Qt, and Apple SDK build dependencies. The only required
host toolchain is Apple's Metal compiler, which Apple distributes through
Xcode rather than through a redistributable package.
The standalone build needs no input overrides, temporary source pins, or
`DYLD_LIBRARY_PATH`.
## One-time prerequisites
### Nix
Install Nix in multi-user mode, make sure its daemon is available, and enable
flakes:
```sh
mkdir -p ~/.config/nix
printf '%s\n' 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
nix --version
nix store info
```
Nix normally has `sandbox = false` on macOS. This build must retain that
setting because RISC Zero invokes Apple's host-installed Metal toolchain. The
preflight script below reports an actionable error if the daemon is configured
otherwise.
### Xcode and the Metal Toolchain
Apple Command Line Tools alone do not include `metal` and `metallib`. Install
the newest full Xcode release supported by the installed macOS from the App
Store or [Apple Developer Downloads](https://developer.apple.com/download/all/).
This requires an Apple account and cannot be automated by the repository.
After installation:
```sh
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license accept
sudo xcodebuild -runFirstLaunch
xcodebuild -downloadComponent MetalToolchain
xcrun --sdk macosx --find metal
xcrun --sdk macosx --find metallib
```
The last two commands must print executable paths. Do not use
`RISC0_SKIP_BUILD_KERNELS=1`: the pinned dependency graph requires the real
Metal kernels.
### Disk space and preflight
Leave at least 60 GiB free before downloading Xcode. Once Xcode is installed,
keep at least 40 GiB free for the first Nix build. Later builds reuse the Nix
store.
Run the complete machine check from `apps/amm`:
```sh
bash scripts/check-macos-prerequisites.sh
```
## Build and run
From `apps/amm`:
```sh
nix build .
nix run .
```
The first cold build is large because it includes Qt, the wallet, the AMM
client, and the RISC Zero Metal kernels. The first start opens without a
wallet; select **Connect** to create or open `~/.lee/wallet/`.
## Why the flake is structured this way
Nix 2.25 rejects committed lock entries for unlocked relative flake inputs such
as `path:../shared/wallet` and `path:../..`. Newer Nix versions may accept that
layout, which is why it can appear to work on one developer's Mac and fail on
another.
The portable layout avoids both relative flake inputs:
- the shared wallet is passed to CMake as an ordinary Nix source path;
- the repository-root AMM client package is imported directly with the same
locked `nixpkgs` and Crane inputs;
- `flake.lock` contains only immutable upstream sources.
The app output uses the builder's full-library QML package layout. The current
`qt-plugin` layout copies the UI plugin and replica factory but omits sibling
external libraries such as `libamm_client`. The module metadata still selects
the C++ backend, so this packaging choice does not change process isolation or
runtime behavior.
Do not replace these with a pull-request URL, self-reference, or local
`path:` flake input. A normal `nix flake lock`, `nix build`, and `nix run`
must work without overrides.
## Known-good validation host
This configuration has been exercised on:
- Apple Silicon (`arm64`)
- macOS 26.5.2
- Xcode 26.6 (build 17F113)
- Metal Toolchain 32023.883
- Nix 2.25.3
The flake also evaluates its complete package and app outputs for
`x86_64-darwin`; an Intel Mac is still required for native runtime validation.
## Recovery
### Nix is unavailable
Open a new terminal after installing Nix. For the standard multi-user
installation, check the store and daemon before trying to bootstrap it again:
```sh
test -x /nix/var/nix/profiles/default/bin/nix
nix store info
sudo launchctl print system/org.nixos.nix-daemon
```
`launchctl bootstrap` can report an I/O error when a service is already loaded
or in an inconsistent state; that message alone does not prove the daemon is
stopped.
### `xcrun` cannot find `metal`
The selected developer directory points at Command Line Tools, or Xcode lacks
the separately downloaded Metal Toolchain component. Repeat the Xcode setup
and verification commands above.
Apple does not distribute the Metal compiler as a standalone redistributable
SDK, so Nix cannot install this host component.
### The build reports that the macOS sandbox blocks Metal
Confirm the active setting:
```sh
nix config show sandbox
```
For this native macOS build it must be `false`. If a machine-wide policy set it
to `true`, update that Nix installation's daemon configuration and restart the
daemon before retrying.
### The first build runs out of space
Remove only disposable build outputs, then collect dead Nix store paths:
```sh
rm -rf /private/tmp/amm-build
nix-store --gc
```
Do not remove `/nix/store` directly.
### A lock error mentions an unlocked input
Restore the committed app lock and verify it without rewriting it:
```sh
git restore flake.lock
nix flake metadata --no-update-lock-file .
```
If the error still names `path:../shared/wallet` or `path:../..`, the checkout
does not contain the portable flake fix described above.

View File

@ -50,6 +50,9 @@ nix profile install 'github:logos-co/logos-package-manager#cli'
This makes `lgpm` available as a global command.
For macOS prerequisites, the Metal toolchain requirement, and common Nix
recovery steps, see [macOS setup](MACOS.md).
## Running the UI standalone
Start the UI with:
@ -76,9 +79,11 @@ nix build '.#lgx' --out-link result-lgx
# Portable variant (self-contained, works without nix)
nix build '.#lgx-portable' --out-link result-lgx-portable
# The core wallet module it depends on. Use the same rev this app pins as the
# `logos_execution_zone` input in flake.nix so the ImageID/ABI match.
nix build 'github:logos-blockchain/logos-execution-zone-module?rev=d2e9400ac06c3cdbfc2405b4f153fff9841a453c#lgx' \
# The core wallet module it depends on. These are the same immutable upstream
# revisions used by this app's flake, including the merged macOS Metal fix.
nix build 'github:logos-blockchain/logos-execution-zone-module?rev=d70225ced646934d2294fd9e8f8b03615c104b80#lgx' \
--override-input logos-execution-zone \
'github:logos-blockchain/logos-execution-zone?rev=a7e06a660940a00093b1760560d37ff84aff5a05' \
--out-link result-core
```

View File

@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
alloy-primitives = { version = "1", default-features = false }
amm_core = { workspace = true }
borsh = { workspace = true }
clock_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc6" }
clock_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0" }
hex = "0.4"
nssa_core = { workspace = true }
risc0-zkvm = { workspace = true }

920
apps/amm/flake.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,8 @@
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
# Shared C++ wallet access and Logos.Wallet QML sources.
shared_wallet = {
url = "path:../shared/wallet";
flake = false;
};
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-small";
crane.url = "github:ipetkov/crane/v0.23.4";
# Core wallet module (the LEZ wallet FFI Qt plugin). The input name must
# match the metadata.json `dependencies` entry so the builder can resolve
@ -17,47 +13,67 @@
logos_execution_zone = {
url = "github:logos-blockchain/logos-execution-zone-module?rev=d70225ced646934d2294fd9e8f8b03615c104b80";
# The module pins the monorepo at v0.2.0-rc6 (e37876a), which owns the
# xcrun wrapper in its flake.nix. Override that transitive input to the
# head of PR #629 (fix(macos): nuke xcrun cache) so the Metal/xcrun build
# works on macOS. Note: #629 branches off `dev`, so this also pulls dev's
# drift from rc6 — if the wallet_ffi ABI mismatches the module build, fall
# back to cherry-picking #629's one line onto e37876a and pin that commit.
# The module pins the monorepo at v0.2.0-rc6 (e37876a). Override that
# transitive input with the merged macOS xcrun-cache fix.
inputs.logos-execution-zone.url =
"github:logos-blockchain/logos-execution-zone?rev=a7e06a660940a00093b1760560d37ff84aff5a05";
};
amm_client.url = "path:../..";
};
outputs = inputs@{ logos-module-builder, shared_wallet, ... }:
logos-module-builder.lib.mkLogosQmlModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
preConfigure = ''
cmakeFlagsArray+=("-DLOGOS_WALLET_SOURCE_DIR=${shared_wallet}")
'';
externalLibInputs = {
amm_client = {
input = inputs.amm_client;
packages.default = "amm_client";
};
outputs = inputs@{ logos-module-builder, nixpkgs, crane, ... }:
let
ammClientInput = (import ../../flake.nix).outputs {
inherit nixpkgs crane;
};
postInstall = ''
# The builder installs the view under lib/qml after this hook. Its
# import descriptor points back to this compiled shared QML module.
test -f ${./qml}/Logos/Wallet/qmldir
moduleBuild = logos-module-builder.lib.mkLogosQmlModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
preConfigure = ''
cmakeFlagsArray+=("-DLOGOS_WALLET_SOURCE_DIR=${../shared/wallet}")
'';
externalLibInputs = {
amm_client = ammClientInput;
};
postInstall = ''
# The builder installs the view under lib/qml after this hook. Its
# import descriptor points back to this compiled shared QML module.
test -f ${./qml}/Logos/Wallet/qmldir
walletQmlDir="shared-wallet/qml/Logos/Wallet"
if [ ! -d "$walletQmlDir" ]; then
echo "Built Logos.Wallet QML module not found"
exit 1
fi
walletQmlInstallDir="$out/lib/Logos/Wallet"
mkdir -p "$walletQmlInstallDir"
cp -r "$walletQmlDir/." "$walletQmlInstallDir/"
test -f "$walletQmlInstallDir/qmldir"
'';
walletQmlDir="shared-wallet/qml/Logos/Wallet"
if [ ! -d "$walletQmlDir" ]; then
echo "Built Logos.Wallet QML module not found"
exit 1
fi
walletQmlInstallDir="$out/lib/Logos/Wallet"
mkdir -p "$walletQmlInstallDir"
cp -r "$walletQmlDir/." "$walletQmlInstallDir/"
test -f "$walletQmlInstallDir/qmldir"
'';
};
publicApps = logos-module-builder.lib.common.forAllSystems nixpkgs ({ system, pkgs }:
{
default = logos-module-builder.lib.mkStandaloneApp {
inherit pkgs;
standalone =
logos-module-builder.inputs.logos-standalone-app.packages.${system}.default;
plugin = moduleBuild.packages.${system}.default;
metadataFile = ./metadata.json;
# The qt-plugin layout only copies the plugin and replica factory.
# Keep the complete lib tree so sibling external libraries such as
# libamm_client remain available through the plugin's loader path.
format = "qml";
moduleDeps =
logos-module-builder.lib.common.collectAllModuleDeps
system inputs moduleBuild.config.dependencies;
};
}
);
in
moduleBuild // {
apps = publicApps;
};
}

View File

@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Verify the host requirements for a supported native macOS AMM build.
set -euo pipefail
failures=0
pass() {
printf 'ok: %s\n' "$1"
}
fail() {
printf 'missing: %s\n' "$1" >&2
failures=$((failures + 1))
}
if [[ "$(uname -s)" != "Darwin" ]]; then
printf 'missing: this script must run on macOS\n' >&2
exit 1
fi
architecture="$(uname -m)"
case "$architecture" in
arm64|x86_64)
pass "supported architecture $architecture"
;;
*)
fail "unsupported architecture $architecture"
;;
esac
developer_dir="$(xcode-select -p 2>/dev/null || true)"
if [[ "$developer_dir" == *.app/Contents/Developer ]]; then
pass "full Xcode selected at $developer_dir"
required_gib=40
else
fail "full Xcode is not selected (current developer directory: ${developer_dir:-none})"
required_gib=60
fi
if xcode_version="$(xcodebuild -version 2>/dev/null)"; then
pass "$(printf '%s' "$xcode_version" | tr '\n' ' ')"
else
fail "xcodebuild is unavailable; install Xcode.app and select it with xcode-select"
fi
if xcodebuild -checkFirstLaunchStatus >/dev/null 2>&1; then
pass "Xcode first-launch setup and licence are complete"
else
fail "Xcode setup is incomplete; run sudo xcodebuild -license accept and -runFirstLaunch"
fi
for tool in metal metallib; do
if tool_path="$(xcrun --sdk macosx --find "$tool" 2>/dev/null)" &&
[[ -x "$tool_path" ]]; then
pass "$tool at $tool_path"
else
fail "$tool is unavailable; run xcodebuild -downloadComponent MetalToolchain"
fi
done
nix_bin="$(command -v nix 2>/dev/null || true)"
if [[ -z "$nix_bin" && -x /nix/var/nix/profiles/default/bin/nix ]]; then
nix_bin=/nix/var/nix/profiles/default/bin/nix
fi
if [[ -n "$nix_bin" ]]; then
pass "Nix at $nix_bin ($("$nix_bin" --version))"
configured_features="$(
"$nix_bin" config show experimental-features 2>/dev/null ||
"$nix_bin" show-config 2>/dev/null |
awk -F ' = ' '$1 == "experimental-features" { print $2 }'
)"
if [[ " $configured_features " == *" nix-command "* &&
" $configured_features " == *" flakes "* ]]; then
pass "Nix flakes and nix-command are enabled"
else
fail "enable experimental-features = nix-command flakes in nix.conf"
fi
if "$nix_bin" --extra-experimental-features 'nix-command flakes' \
store info >/dev/null 2>&1; then
pass "Nix daemon and store are available"
else
fail "Nix cannot contact its daemon or store"
fi
sandbox_setting="$("$nix_bin" config show sandbox 2>/dev/null || true)"
if [[ "$sandbox_setting" == "false" ]]; then
pass "Nix macOS sandbox is disabled for the host Metal toolchain"
else
fail "Nix sandbox must be false for the host Metal toolchain (current: ${sandbox_setting:-unknown})"
fi
else
fail "Nix is unavailable; install multi-user Nix and start its daemon"
fi
if [[ -d /nix ]]; then
available_kib="$(df -Pk /nix | awk 'NR == 2 { print $4 }')"
required_kib=$((required_gib * 1024 * 1024))
if [[ "${available_kib:-0}" -ge "$required_kib" ]]; then
pass "at least ${required_gib} GiB free on the Nix volume"
else
fail "less than ${required_gib} GiB free on the Nix volume"
fi
else
fail "/nix is not mounted"
fi
if (( failures > 0 )); then
printf '\nSee MACOS.md for installation and recovery instructions.\n' >&2
exit 1
fi
printf '\nAll macOS AMM prerequisites are available.\n'