| name | build-mila-locally |
| description | Use when building, compiling, or running the Mila app locally from source on a Mac — debug build, release build, self-signed DMG, or installing a locally-built Mila into /Applications. Covers "build Mila", "run Mila locally", "make a DMG", "install my local build", and the common version/signing/PythonRuntime build failures. |
Build Mila Locally
Overview
Mila builds via XcodeGen (project.yml is the source of truth, NOT the .xcodeproj) driven by a Makefile. Local builds are signed with the persistent self-signed "Mila Local Dev" cert when it exists in the login keychain (created on first run of scripts/install-debug.sh), falling back to ad-hoc ("Sign to Run Locally") otherwise. The stable cert matters: macOS TCC keys mic/screen-recording grants on the signing identity, so ad-hoc builds re-prompt for recording permission after every install. Notarized Developer-ID builds come from a separate private pipeline and are out of scope here.
All build commands run from the repo root (wherever your checkout lives).
Quick Reference
| Goal | Command | Output |
|---|
| Generate Xcode project | make project | Mila.xcodeproj (regenerated from project.yml) |
| Debug build | make build | build/Build/Products/Debug/Mila.app |
| Build and launch | make run | builds Debug, then opens it |
| Release build | make release-build | build-release/Build/Products/Release/Mila.app |
| Self-signed DMG | make dmg VERSION=<x.y.z> | Mila-<x.y.z>.dmg (Mila Local Dev cert if present, else ad-hoc) |
| Run tests | make test | XCTest run |
| Clean | make clean | removes Mila.xcodeproj, build, build-release, *.dmg |
make targets chain: dmg → release-build → project → bootstrap, so a single command regenerates the project and builds.
CRITICAL: make dmg needs an explicit VERSION
make dmg fails on a clean checkout with:
/bin/sh: MARKETING_VERSION: command not found
./scripts/make-dmg.sh: ... usage: make-dmg.sh APP_PATH DMG_PATH VERSION
Why: the Makefile's VERSION fallback reads CFBundleShortVersionString from Info.plist, but that key stores the literal $(MARKETING_VERSION) (Xcode resolves it only at build time). Make passes that literal into the recipe, the shell tries to command-substitute $(MARKETING_VERSION) — hence the MARKETING_VERSION: command not found line — and VERSION collapses to an empty string. scripts/make-dmg.sh then aborts on its empty-VERSION guard (${3:?usage…}). The release build itself SUCCEEDS — only DMG packaging breaks.
Fix: the canonical version lives in project.yml (MARKETING_VERSION). Pass it explicitly:
VERSION=$(awk -F'"' '/^[[:space:]]*MARKETING_VERSION:/{print $2; exit}' project.yml)
[ -n "$VERSION" ] || { echo "MARKETING_VERSION not found in project.yml" >&2; exit 1; }
make dmg VERSION="$VERSION"
Other known build gotchas
Verifying the signature
A correct local build is signed with the Mila Local Dev cert (or ad-hoc if that cert was never created) and carries the app entitlements:
codesign -dv /Applications/Mila.app 2>&1 | grep -E "Identifier|Signature|Authority"
codesign -d --entitlements - /Applications/Mila.app 2>/dev/null | grep audio-input
Installing a local build into /Applications
A make run/make dmg build lives in the repo's build/ folder — it runs fine but won't appear in the Applications folder. To install it like a normal app:
MNT=$(mktemp -d)
hdiutil attach Mila-<x.y.z>.dmg -nobrowse -mountpoint "$MNT" -quiet
[ -e /Applications/Mila.app ] && trash /Applications/Mila.app
cp -R "$MNT/Mila.app" /Applications/
hdiutil detach "$MNT" -quiet
open -a /Applications/Mila.app
Ad-hoc-signed apps copied (not downloaded) have no quarantine flag, so they launch without the Gatekeeper right-click dance. If the app WAS downloaded, Gatekeeper shows a "right-click → Open" prompt on first launch.
TCC / recording-permission survival — re-sign if needed. macOS keys mic and screen-recording grants on the app's signing identity + entitlements. make dmg already signs with the "Mila Local Dev" cert and Mila/Resources/Mila.entitlements when the cert exists in the login keychain, so a fresh DMG installs cleanly over a scripts/install-debug.sh install without re-prompting. But if the installed app is ad-hoc signed or missing entitlements (older DMG, cert created after the DMG was built — symptom: macOS asks for recording permission on every use), re-sign it in place:
SHA=$(security find-certificate -c "Mila Local Dev" -a -Z \
~/Library/Keychains/login.keychain-db 2>/dev/null | awk '/SHA-1 hash/ {print $NF}' | head -1)
if [ -z "$SHA" ]; then
echo "no 'Mila Local Dev' cert — run scripts/install-debug.sh once to create it" >&2
else
codesign --force --sign "$SHA" \
--entitlements Mila/Resources/Mila.entitlements /Applications/Mila.app
fi
If the cert doesn't exist yet, run scripts/install-debug.sh once to create it (or accept ad-hoc + per-install re-prompts).
Common Mistakes
- Running
make dmg without VERSION= → the MARKETING_VERSION: command not found failure above. Always pass it.
- Editing
Mila.xcodeproj directly → overwritten on next make project. Edit project.yml instead.
- Hardcoding a version in
Info.plist → it must stay $(MARKETING_VERSION) / $(CURRENT_PROJECT_VERSION); bump versions only in project.yml.
- Expecting a Developer-ID / notarized build → not available locally; local builds sign with the Mila Local Dev cert or ad-hoc. (
CODESIGN_IDENTITY=- make dmg forces ad-hoc, e.g. to test the Gatekeeper first-launch prompt.)
rm-ing an old /Applications/Mila.app → use trash so it's recoverable.