Skip to content

Add native AArch64 package build support - #240

Open
riverscn wants to merge 8 commits into
omacom:masterfrom
riverscn:upstream-aarch64-build-support
Open

Add native AArch64 package build support#240
riverscn wants to merge 8 commits into
omacom:masterfrom
riverscn:upstream-aarch64-build-support

Conversation

@riverscn

@riverscn riverscn commented Aug 30, 2026

Copy link
Copy Markdown

Summary

This pull request adds native AArch64 support to the existing Omarchy package
toolchain while preserving the current x86_64 behavior and release design.

  • Bootstrap an Arch Linux ARM builder image and thread the target architecture
    through build, signing, repository-index, cleanup, and removal operations.
  • Support architecture-specific dependency arrays, split-package outputs, and
    dependency ordering through package names and virtual provides.
  • Extend 27 existing recipes and add 20 package directories needed for the
    AArch64 build/runtime closure and application set.
  • Keep every package-specific exception in its package directory through
    standard PKGBUILD fields, .omarchy/patches, and post-sync.sh hooks.
  • Add a bounded recursive final-package audit for ELF, Mach-O, native PE,
    managed ECMA-335 assemblies, ASAR, and libarchive-supported payloads before
    AArch64 artifacts enter the repository.
  • Allow deliberate cross-platform runtime data only through exact relative
    paths and SHA-256-pinned file/container reviews, including output-specific
    policy for split PKGBUILDs.
  • Add metadata/dry-run regression coverage and maintainer-facing documentation.
  • Allow release backends to inject exactly one fingerprint-pinned repository
    public key before pacman synchronizes a signed baseline.

Operators retain the existing command shape, for example:

bin/repo build --arch aarch64 --mirror edge

Outputs remain isolated under <channel>/aarch64.

Design boundaries

This is intentionally not a release-system rewrite. It does not add an
emulated CI build, change the scheduler, replace the edge/rc/stable release
train, introduce a downstream signing key, or include the fork's GitHub Release
adapter. Native AArch64 production scheduling and multi-architecture release
coordination are documented as follow-up work rather than hidden in this PR.

The existing optional cross-architecture behavior is left intact, but all
acceptance work for this change was performed on native AArch64 hardware with
no QEMU or binfmt emulation.

Package scope

The source tree contains 136 package bases after this PR. Their AArch64 status
is deliberately explicit:

Status Package bases Review meaning
Already AArch64-compatible 69 Included in the validated range without recipe changes
Existing recipes adapted for AArch64 27 Gain architecture-specific sources, dependencies, patches, or sync hooks
Package directories added 20 Fill the build/runtime closure, application set, and Libretro set
Explicitly excluded 20 Remain outside the AArch64 build plan for documented vendor, dependency, or hardware reasons

The supported range is therefore 116 package bases: 69 unchanged, 27 adapted,
and 20 added. dropbox-cli belongs to the excluded group, but its existing
recipe is changed from any to x86_64 so its real runtime constraint is not
misrepresented. This is why the Git diff touches 28 existing package
directories while only 27 are AArch64 adaptations.

27 existing recipes adapted for AArch64

1password, asdcontrol, cursor-bin, cursor-cli,
github-copilot-cli, grok-bot, hermes-desktop,
heroic-games-launcher-bin, hyprland-preview-share-picker,
libretro-uae-git, limine-mkinitcpio-hook, lmstudio-bin, omasnap,
openai-codex-desktop, qmk-hid, rustdesk, sunshine, symfony-cli,
t3code-bin, tensaku, typora, tzupdate, v4l2-relayd,
visual-studio-code-bin, voxtype-bin, xpadneo-dkms, and yt6801-dkms.

20 package directories added
  • Build/runtime closure: bindfs, dotnet-runtime-bin (pkgbase
    dotnet-core-bin), gradle, gtk-engine-murrine, and gtk2.
  • Applications and command-line tools: bitwarden, brave-bin,
    brave-origin-bin, ghostty, google-chrome, obsidian, ollama,
    pandoc-cli, pinta, zed, and zen-browser-bin.
  • Libretro cores: libretro-blastem, libretro-desmume,
    libretro-kronos, and libretro-ppsspp.
20 package bases explicitly excluded from AArch64
  • Vendor x86_64-only applications: dropbox, makima-bin,
    minecraft-launcher, spotify, and tmog-bin.
  • Unsatisfied Dropbox runtime dependency: dropbox-cli and
    nautilus-dropbox.
  • x86/NVIDIA compatibility or driver bundles: lib32-nvidia-580xx-utils and
    nvidia-580xx-utils.
  • x86 laptop/platform enablement: asusctl,
    dell-xps-touchpad-haptics, dell-xps13-sidecar-amps,
    intel-ipu7-camera, libfprint-git, macbook12-spi-driver-dkms,
    macbook8-spi-pxa2xx-nodma-dkms, supergfxctl, and
    tuxedo-drivers-nocompatcheck-dkms.
  • Architecture-specific alternatives required: linux-ptl and
    t3code-patched-bin (the supported ARM path is t3code-bin).

Notable package and tooling decisions

  • Every final AArch64 package is recursively audited before it enters the build
    repository. Wrong-architecture ELF and unreviewed Mach-O, native PE, or DOS
    executables fail closed; extraction depth, files, bytes, and time are bounded.
  • ECMA-335 assemblies are counted separately from native Windows PE. The
    dotnet-sdk-bin split output retains five Microsoft Windows-target debugging
    tools under an output-specific, exact path-and-digest policy; the other five
    outputs from the same PKGBUILD receive no exception.
  • Gradle's cross-platform native JAR matrix and Heroic's two Wine integration
    shims are product runtime data rather than Linux host executables. Each
    retained file/container is checksum-pinned; a changed or unused entry fails.
  • Typora's official ARM64 Debian archive carries an unused macOS x86_64
    cld.node. The AArch64 recipe removes that exact payload, accepts a future
    AArch64 ELF replacement, and rejects any unknown replacement.
  • Cursor, GitHub Copilot CLI, T3 Code, and Visual Studio Code remove unreachable
    Windows/macOS prebuilds. Their recipes first require the corresponding Linux
    ARM64 native files where applicable, so cleanup cannot hide a missing native
    implementation.
  • VS Code currently retains one optional x64 apply-seccomp helper inside the
    vendor ASAR because the ARM64 archive has no replacement and rewriting the
    ASAR would change the vendor container. Its exact nested bytes are reviewed;
    the missing ARM64 helper remains a documented vendor limitation.
  • Package outputs are taken from makepkg --packagelist. This fixes a generic
    bug where a recipe consuming an official .pkg.tar.zst source could have
    that source archive mistaken for a newly built package.
  • Signed repository baselines can provide an explicit public-key path and
    expected fingerprint. The builder imports only that single matching key
    before pacman synchronization; it does not embed a downstream key or disable
    signature verification.
  • dropbox-cli is explicitly constrained to x86_64. Its Python code is
    portable, but it cannot operate without Dropbox's proprietary x86_64-only
    daemon, so publishing it as any would create an unsatisfiable AArch64
    package.
  • RustDesk keeps the existing x86_64 source-build path. Its AArch64 path follows
    the dependencies and install layout used by AUR rustdesk-bin and consumes
    RustDesk's official ARM64 RPM.
  • Ghostty uses a pinned native Zig toolchain and a bounded three-attempt retry
    for its dependency-cache fetch. The sync hook fails closed if its upstream
    baseline changes.
  • Zed retains the vendor's already-stripped binaries with !strip, avoiding a
    second strip pass over custom ELF sections.
  • Architecture changes for Libretro, Limine, VS Code/Copilot, Heroic, Voxtype,
    and other exceptional packages remain package-local and are documented
    individually.

Native validation

The repository's normal Docker entry point was exercised on a native AArch64
host. The complete upstream-aligned range contained 116 package bases and
produced 145 archives: 112 declared aarch64 and 33 declared any.

That zero-baseline validation:

  • inspected 92,505 regular files and 562 direct ELF files; every ELF was
    AArch64;
  • verified package and repository signatures with an ephemeral local key;
  • required the package archives, audit, manifest, and repository database to
    name the same package set;
  • checked all generated SHA-256 entries; and
  • resolved a separate pacman dependency transaction for every scoped output.

Those original counts predate the multi-format recursive gate, so they prove
native construction, direct ELF architecture, signing, completeness, and
dependency resolution, but not opaque nested-container contents.

A later read-only audit recursively inspected all 147 archives in the fork's
published edge snapshot, expanding 288,990 files (21.8 GB) and 424 containers.
It identified normal managed .NET assemblies, Gradle and Heroic's deliberate
cross-platform data, one file(1) false positive, and the five removable
foreign payload classes now fixed in Cursor, Copilot CLI, T3 Code, Typora, and
VS Code.

The stable snapshot was downloaded and verified independently because its 145
archive hashes differed from edge. All 145 matched the published checksums
before scanning. The recursive audit expanded 287,417 files (21.8 GB) and 422
containers: 140 historical archives passed the hardened policy and the same
five pre-fix package revisions failed, with no stable-only finding.

Targeted native builds through the normal Docker entry point then passed the
hardened build gate for Typora, Cursor, Copilot CLI, T3 Code, VS Code, and all
six outputs of the .NET split PKGBUILD. Cursor, Copilot, T3, and Typora had no
foreign executable after cleanup. VS Code retained only its one exact reviewed
nested helper. The .NET SDK selected its five-file output policy while its
other outputs used no exception. Gradle and Heroic's published payloads also
passed with their exact container/file reviews. No QEMU, GitHub Actions build,
release mutation, production signing key, or production repository write was
used for this acceptance work.

A signed-baseline rehearsal additionally exercised the generic key handoff.
The correct pinned fingerprint allowed pacman to synchronize the signed
AArch64 database and skip an up-to-date package; a deliberately incorrect
fingerprint failed before repository synchronization.

Static coverage is available through:

./test/audit-package-architecture-test.sh
./test/aarch64-support-test.sh

The metadata test verifies the 47 package bases added or adapted by this PR;
the architecture-audit test covers nested containers, managed PE, exact file
and container policies, stale/digest-mismatched entries, both target
architectures, false-positive archive signatures, and resource limits.

Review guide

The history is organized into eight reviewable commits:

  1. make the shared package tooling architecture-aware;
  2. extend existing recipes;
  3. add the missing AArch64 package recipes;
  4. add tests and documentation;
  5. add the fingerprint-pinned signed-repository trust bootstrap;
  6. add recursive final-payload auditing and the Typora cleanup; and
  7. harden nested multi-format audits, exact review policies, and the remaining
    affected package recipes; and
  8. install git in the Actions test container used by the architecture patch
    checks.

docs/aarch64-package-notes.md documents every package-level exception and its
sync/update strategy. docs/aarch64-follow-up.md records the validation,
explicit exclusions, vendor limitations, and the remaining production
edge/rc/stable rollout work.

@themartiano

Copy link
Copy Markdown

I was doing something similar and I've found out that some ARM packages are not fully compatible and contain x86 binaries inside.
I built a scanner that recursively unpacks those to make sure they're fully compatible. EG Typora isn't
If you want I can share more details and implementation

@riverscn

Copy link
Copy Markdown
Author

I was doing something similar and I've found out that some ARM packages are not fully compatible and contain x86 binaries inside. I built a scanner that recursively unpacks those to make sure they're fully compatible. EG Typora isn't If you want I can share more details and implementation

Thanks — I ran into the same issue while working on this PR. I added an audit that unpacks every built package, checks its ELF files, and fixed the foreign ELF payloads it found. However, my current audit doesn’t yet recursively inspect nested containers or cover non-ELF formats. Typora is a good example: its Linux binaries are AArch64, but the package still contains an x86_64 Mach-O Node addon.
I’d definitely be interested in comparing our implementations and results so we can cross-check the full package set and make sure neither scanner is missing anything.

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.

2 participants