Skip to content

Instantly share code, notes, and snippets.

@lydell
Created October 13, 2026 12:38
Show Gist options
  • Select an option

  • Save lydell/5095bf0043752198a980a736bbecec99 to your computer and use it in GitHub Desktop.

Select an option

Save lydell/5095bf0043752198a980a736bbecec99 to your computer and use it in GitHub Desktop.
Reproduce official Elm macOS ARM binaries
#!/usr/bin/env python3
"""
Rebuild an official Elm macOS arm64 release binary as closely as possible,
and compare the result with the official binary.
Usage (from an elm/compiler checkout):
./reproduce-elm-mac-arm.py [VERSION] [--official PATH] [--repo PATH] [--out DIR]
What it does:
1. Downloads the official binary (or uses --official).
2. Reads from the official binary:
- the exact dependency versions (from the GHC unit IDs embedded in it),
- the builder's HOME directory (embedded via cabal's Paths_* modules),
- the linker version and the code signing timestamp.
3. Exports the VERSION git tag from the repo (the repo is not modified).
4. Builds with a fake HOME of the same length as the builder's HOME, so that
cabal uses the same directory layout, with the dependency versions pinned
and the Hackage index state set to the signing time.
5. Strips the binary, replaces the fake HOME with the builder's HOME (same
length, so nothing moves), copies the official LC_UUID (ld derives it from
the contents before patching, so it necessarily reflects the fake HOME),
and ad-hoc signs it so it can run.
6. Compares the result with the official binary (signatures removed). If the
build is reproducible, they are byte-for-byte identical.
Requires: ghcup-installed ghc and cabal matching the official build.
Written by Claude Opus 5.5.
"""
import argparse
import datetime
import gzip
import json
import os
import re
import shutil
import subprocess
import sys
import urllib.request
UNIT_ID = re.compile(rb"^([A-Za-z][A-Za-z0-9-]*)-([0-9]+(?:\.[0-9]+)*)-[0-9a-f]{8}$")
CABAL_BIN = re.compile(rb"^(/.+)/\.cabal/bin$")
ABI_TAG = re.compile(rb"/\.cabal/lib/aarch64-osx-(ghc-[0-9.]+-[0-9a-f]+)$")
def log(message):
print(f"\n==> {message}", flush=True)
def warn(message):
print(f"WARNING: {message}", file=sys.stderr, flush=True)
def die(message):
print(f"ERROR: {message}", file=sys.stderr, flush=True)
sys.exit(1)
def run(cmd, env=None, cwd=None, capture=False):
print("$ " + " ".join(cmd), flush=True)
result = subprocess.run(
cmd, env=env, cwd=cwd, text=True,
stdout=subprocess.PIPE if capture else None,
stderr=subprocess.STDOUT if capture else None,
)
if result.returncode != 0:
if capture:
print(result.stdout)
die(f"Command failed with exit code {result.returncode}")
return result.stdout
def output(cmd, env=None):
result = subprocess.run(cmd, env=env, capture_output=True, text=True)
return result.stdout + result.stderr
def strings(path):
data = subprocess.run(["strings", "-a", path], capture_output=True).stdout
return data.splitlines()
def shorten(name):
# cabal drops lowercase vowels from package names in unit IDs on macOS,
# to keep paths short.
return re.sub("[aeiou]", "", name)
def unit_ids(lines):
return {line.decode() for line in lines if UNIT_ID.match(line)}
def ld_version(path):
match = re.search(r"LC_BUILD_VERSION.*?tool 3\n\s*version (\S+)", output(["otool", "-l", path]), re.S)
return match.group(1) if match else None
def uuid(path):
match = re.search(r"^\s*uuid (\S+)$", output(["otool", "-l", path]), re.M)
return bytes.fromhex(match.group(1).replace("-", "")) if match else None
def build_version(path):
text = output(["otool", "-l", path])
match = re.search(r"minos (\S+)\n\s*sdk (\S+)", text)
return match.groups() if match else None
def signing_time(path):
text = output(["codesign", "-dvv", path], env={**os.environ, "LC_ALL": "C"})
match = re.search(r"^Timestamp=(.+)$", text, re.M)
if not match:
return None
try:
local = datetime.datetime.strptime(match.group(1), "%d %b %Y at %H:%M:%S")
except ValueError:
return None
return local.astimezone().astimezone(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def pick_developer_dir(wanted_ld):
candidates = []
if os.environ.get("DEVELOPER_DIR"):
candidates.append(os.environ["DEVELOPER_DIR"])
candidates.append(output(["xcode-select", "-p"]).strip())
candidates.append("/Library/Developer/CommandLineTools")
found = []
for directory in dict.fromkeys(candidates):
if not os.path.isdir(directory):
continue
text = output(["xcrun", "ld", "-v"], env={**os.environ, "DEVELOPER_DIR": directory})
match = re.search(r"PROJECT:ld-(\S+)", text)
version = match.group(1) if match else "unknown"
found.append(f"{directory} (ld {version})")
if version == wanted_ld:
return directory
warn(f"No developer directory has ld {wanted_ld}. Found: {', '.join(found)}")
return candidates[-1] if os.path.isdir(candidates[-1]) else candidates[0]
def fake_home_for(real_home):
# Must be the same length as the builder's HOME, so the binary can be
# patched without moving anything.
base = "/tmp/elm-reproduce-home"
if len(real_home) < len("/tmp/x"):
die(f"Builder HOME {real_home!r} is too short to mirror under /tmp")
return (base + "x" * len(real_home))[:len(real_home)]
def remove_signature(source, target):
shutil.copyfile(source, target)
run(["codesign", "--remove-signature", target])
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("version", nargs="?", default="0.19.3")
parser.add_argument("--official", help="path to the (gunzipped) official binary; downloaded if omitted")
parser.add_argument("--repo", default=".", help="elm/compiler git checkout (default: current directory)")
parser.add_argument("--out", default="elm-reproduce-out", help="output directory (default: ./elm-reproduce-out)")
args = parser.parse_args()
if os.uname().sysname != "Darwin" or os.uname().machine != "arm64":
die("This script only reproduces the macOS arm64 binary, on macOS arm64.")
out = os.path.abspath(args.out)
os.makedirs(out, exist_ok=True)
repo = os.path.abspath(args.repo)
# 1. Official binary.
official = os.path.join(out, f"elm-{args.version}-mac-arm-official")
if args.official:
shutil.copyfile(args.official, official)
else:
url = f"https://github.com/elm/compiler/releases/download/{args.version}/elm-{args.version}-mac-arm.gz"
log(f"Downloading {url}")
with urllib.request.urlopen(url) as response:
data = gzip.decompress(response.read())
with open(official, "wb") as file:
file.write(data)
os.chmod(official, 0o755)
# 2. Analyze it.
log("Analyzing the official binary")
official_strings = strings(official)
official_units = unit_ids(official_strings)
homes = {m.group(1) for line in official_strings if (m := CABAL_BIN.match(line))}
abi_tags = {m.group(1).decode() for line in official_strings if (m := ABI_TAG.search(line))}
if len(homes) != 1 or len(abi_tags) != 1:
die(f"Could not determine the builder's HOME / GHC ABI tag (found {homes}, {abi_tags})")
real_home = homes.pop().decode()
abi_tag = abi_tags.pop()
official_ld = ld_version(official)
index_state = signing_time(official)
print(f"Builder HOME: {real_home}")
print(f"GHC ABI tag: {abi_tag}")
print(f"Linker version: {official_ld}")
print(f"minos / sdk: {build_version(official)}")
print(f"Signed at: {index_state}")
print(f"Unit IDs found: {len(official_units)}")
fake_home = fake_home_for(real_home)
developer_dir = pick_developer_dir(official_ld)
env = {**os.environ, "HOME": fake_home, "DEVELOPER_DIR": developer_dir}
for name in ["CABAL_DIR", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_STATE_HOME", "XDG_DATA_HOME"]:
env.pop(name, None)
print(f"Fake HOME: {fake_home}")
print(f"DEVELOPER_DIR: {developer_dir}")
os.makedirs(fake_home, exist_ok=True)
if os.path.exists(os.path.join(fake_home, ".cabal")):
die(f"{fake_home}/.cabal exists, which would make cabal use a different layout. Remove it.")
local_abi = run(["cabal", "path", "--compiler-info"], env=env, cwd=fake_home, capture=True)
match = re.search(r"^compiler-abi-tag: (\S+)$", local_abi, re.M)
if not match or match.group(1) != abi_tag:
die(f"Your GHC does not match: need {abi_tag}, have {match and match.group(1)}.\n"
f"Install it with: ghcup install ghc {abi_tag.split('-')[1]}")
# 3. Source.
log(f"Exporting tag {args.version} from {repo}")
src = os.path.join(fake_home, f"src-{args.version}")
os.makedirs(src, exist_ok=True)
archive = subprocess.run(["git", "-C", repo, "archive", f"refs/tags/{args.version}"], capture_output=True)
if archive.returncode != 0:
die(archive.stderr.decode())
subprocess.run(["tar", "-x", "-C", src], input=archive.stdout, check=True)
# 4. Resolve dependency versions.
log("Updating the package index")
index_args = [f"--index-state={index_state}"] if index_state else []
run(["cabal", "update"], env=env, cwd=src)
wanted = {} # shortened name -> version
for unit in official_units:
name, version = UNIT_ID.match(unit.encode()).groups()
wanted[name.decode()] = version.decode()
constraints = {}
for attempt in range(5):
log(f"Resolving dependencies (attempt {attempt + 1})")
constraint_args = [f"--constraint=any.{n} =={v}" for n, v in sorted(constraints.items())]
run(["cabal", "build", "elm", "--dry-run", *index_args, *constraint_args], env=env, cwd=src, capture=True)
with open(os.path.join(src, "dist-newstyle", "cache", "plan.json")) as file:
plan = json.load(file)
by_short = {}
for package in plan["install-plan"]:
if package["type"] == "configured" and package.get("style") != "local":
by_short.setdefault(shorten(package["pkg-name"]), set()).add(package["pkg-name"])
collisions = {k: v for k, v in by_short.items() if len(v) > 1}
if collisions:
die(f"Ambiguous shortened package names: {collisions}")
new = {next(iter(by_short[s])): v for s, v in wanted.items() if s in by_short}
if new == constraints:
break
constraints = new
else:
die("Dependency resolution did not converge")
missing = sorted(set(wanted) - set(by_short))
if missing:
die(f"Packages in the official binary that are not in the build plan: {missing}")
print("Pinned versions:")
for name, version in sorted(constraints.items()):
print(f" {name} == {version}")
# 5. Build.
log("Building")
constraint_args = [f"--constraint=any.{n} =={v}" for n, v in sorted(constraints.items())]
run(["cabal", "build", "elm", "--ghc-option=-split-sections", *index_args, *constraint_args], env=env, cwd=src)
built = run(["cabal", "list-bin", "elm", *index_args, *constraint_args], env=env, cwd=src, capture=True).strip().splitlines()[-1]
rebuilt = os.path.join(out, f"elm-{args.version}-mac-arm-rebuilt")
shutil.copyfile(built, rebuilt)
os.chmod(rebuilt, 0o755)
run(["strip", rebuilt], env=env)
# 6. Patch HOME.
log(f"Replacing {fake_home} with {real_home}")
with open(rebuilt, "rb") as file:
data = file.read()
fake_count = data.count(fake_home.encode())
data = data.replace(fake_home.encode(), real_home.encode())
with open(rebuilt, "wb") as file:
file.write(data)
with open(official, "rb") as file:
real_count = file.read().count(real_home.encode())
print(f"Replaced {fake_count} occurrences (official binary has {real_count})")
official_uuid, rebuilt_uuid = uuid(official), uuid(rebuilt)
log(f"Replacing LC_UUID {rebuilt_uuid.hex()} with {official_uuid.hex()}")
with open(rebuilt, "rb") as file:
data = file.read()
if data.count(rebuilt_uuid) != 1:
die(f"Expected the UUID exactly once in the rebuilt binary, found it {data.count(rebuilt_uuid)} times")
with open(rebuilt, "wb") as file:
file.write(data.replace(rebuilt_uuid, official_uuid))
run(["codesign", "--sign", "-", "--force", rebuilt])
# 7. Compare.
log("Comparing")
problems = []
rebuilt_units = unit_ids(strings(rebuilt))
if rebuilt_units != official_units:
problems.append("unit IDs differ:\n" + "\n".join(
[f" only official: {u}" for u in sorted(official_units - rebuilt_units)] +
[f" only rebuilt: {u}" for u in sorted(rebuilt_units - official_units)]))
else:
print(f"All {len(official_units)} unit IDs match")
if ld_version(rebuilt) != official_ld or build_version(rebuilt) != build_version(official):
problems.append(f"toolchain differs: ld {ld_version(rebuilt)} vs {official_ld}, "
f"minos/sdk {build_version(rebuilt)} vs {build_version(official)}")
if fake_count != real_count:
problems.append(f"HOME occurs {fake_count} times in the rebuilt binary but {real_count} times in the official one")
official_unsigned = os.path.join(out, "official-unsigned")
rebuilt_unsigned = os.path.join(out, "rebuilt-unsigned")
remove_signature(official, official_unsigned)
remove_signature(rebuilt, rebuilt_unsigned)
with open(official_unsigned, "rb") as a, open(rebuilt_unsigned, "rb") as b:
a_data, b_data = a.read(), b.read()
if a_data == b_data:
print("Unsigned binaries are byte-for-byte identical")
else:
differing = sum(x != y for x, y in zip(a_data, b_data))
print(f"Unsigned binaries differ: sizes {len(a_data)} vs {len(b_data)}, {differing} differing bytes")
a_lc = output(["otool", "-l", official_unsigned]).splitlines()[1:]
b_lc = output(["otool", "-l", rebuilt_unsigned]).splitlines()[1:]
for line_a, line_b in zip(a_lc, b_lc):
if line_a != line_b:
print(f" load command: {line_a.strip()!r} vs {line_b.strip()!r}")
problems.append("unsigned binaries are not identical")
log("Result")
print(f"Official binary: {official}")
print(f"Rebuilt binary: {rebuilt}")
if problems:
print("Differences:")
for problem in problems:
print(f"- {problem}")
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment