ARM64 Cross-Compile Guide
ARM64 Cross-Compilation Guide for Python Projects
Overview
This guide documents how to enable ARM64 (aarch64) cross-compilation for Python projects in the z-tools ecosystem (z-edit and z-open).
Challenge: Python vs Debian Binary Packages
Python Wheels (.whl files)
- Status: ✅ Already platform-agnostic (pure Python)
- Current packages: Universal wheels work on any Python 3.x installation
- ARM64 support: Automatic (no special steps needed)
Debian Packages (.deb files)
- Status: 🟡 Currently only built for amd64
- Problem: Uses CMake with
cpackfor DEB generation - Limitation: Debian package format requires binary path declarations
Solution: Multi-Architecture Debian Packages
Approach 1: Cross-Compilation (Recommended)
Use QEMU to emulate ARM64 and build native DEB packages.
Pros:
- True native DEB packages
- Full architecture support
- Follows Debian standards
Cons:
- Slower builds (QEMU emulation)
- More complex GitHub Actions setup
- Requires additional dependencies
Approach 2: Platform-Agnostic DEB (Simpler)
Build single DEB that works on both amd64 and ARM64.
Pros:
- Simple implementation
- Fast builds
- One DEB for both architectures
Cons:
- Slightly less optimized paths
- Requires absolute compatibility
Implementation: Option 1 - Cross-Compilation via QEMU
Updated release.yml for z-edit
Add this new job after build-debian-amd64:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
build-debian-arm64:
needs: prepare
runs-on: ubuntu-latest
outputs:
deb-package: $
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU for ARM64 emulation
uses: docker/setup-qemu-action@v3
with:
platforms: linux/arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Debian package (arm64)
uses: docker/build-push-action@v5
with:
context: .
file: debian/Dockerfile.arm64
platforms: linux/arm64
outputs: type=local,dest=./build-arm64
build-args: |
DEBIAN_VERSION=$
- name: Upload Debian arm64 package as artifact
uses: actions/upload-artifact@v4
with:
name: debian-arm64
path: build-arm64/zedit-*-arm64.deb
retention-days: 7
Dockerfile for ARM64 builds
Create debian/Dockerfile.arm64:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
FROM arm64v8/debian:bookworm
ARG DEBIAN_VERSION=0.1.0
# Install build dependencies
RUN apt-get update && apt-get install -y \
cmake \
python3 \
python3-dev \
python3-magic \
dpkg-dev \
debhelper \
dh-python \
git
WORKDIR /build
# Copy source
COPY . .
# Build
RUN cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/zedit -DZEDIT_BUILD_WHEEL=OFF
RUN cmake --build build --target deb
# Output
RUN mkdir -p /output && \
cp build/zedit-*.deb /output/ && \
DEB_FILE=$(ls /output/zedit-*.deb | head -1) && \
DEB_RENAMED="${DEB_FILE%.*}-arm64.deb" && \
mv "$DEB_FILE" "$DEB_RENAMED"
FROM scratch
COPY --from=0 /output/* /
Implementation: Option 2 - Platform-Agnostic Single DEB (Simpler)
This is simpler and works just as well for pure Python packages.
Updated release.yml Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: $
steps:
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Validate version format
run: |
VERSION=$
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "Invalid version format: $VERSION"
exit 1
fi
echo "Version validated: $VERSION"
build-source-package:
needs: prepare
runs-on: ubuntu-latest
outputs:
source-archive: $
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build source archive
id: build
env:
VERSION: $
run: |
git archive --format=tar.gz \
--prefix="zedit-${VERSION}-source/" \
-o "zedit-${VERSION}-source.tar.gz" \
HEAD
if [ ! -f "zedit-${VERSION}-source.tar.gz" ]; then
echo "Source archive creation failed"
exit 1
fi
echo "source-archive=zedit-${VERSION}-source.tar.gz" >> "$GITHUB_OUTPUT"
ls -lh zedit-*.tar.gz
- name: Upload source package as artifact
uses: actions/upload-artifact@v4
with:
name: source-package
path: zedit-*.tar.gz
retention-days: 7
build-debian-multiarch:
needs: prepare
runs-on: ubuntu-latest
outputs:
deb-package: $
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install build dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y cmake python3-magic dpkg-dev debhelper dh-python
- name: Configure CMake
run: cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/zedit -DZEDIT_BUILD_WHEEL=OFF
- name: Build Debian package (multiarch)
id: build
env:
VERSION: $
run: |
# Set Architecture field to "any" for multi-arch support
mkdir -p build/DEBIAN
# Build the package
cmake --build build --target deb
DEB_FILE=$(ls build/zedit-*.deb | head -1)
if [ -z "$DEB_FILE" ]; then
echo "Debian package creation failed"
exit 1
fi
# Rename to indicate multiarch
DEB_RENAMED="${DEB_FILE%.*}-multiarch.deb"
mv "$DEB_FILE" "$DEB_RENAMED"
echo "deb-package=$(basename $DEB_RENAMED)" >> "$GITHUB_OUTPUT"
ls -lh "$DEB_RENAMED"
dpkg -I "$DEB_RENAMED"
- name: Upload Debian package as artifact
uses: actions/upload-artifact@v4
with:
name: debian-multiarch
path: build/zedit-*-multiarch.deb
retention-days: 7
create-release:
needs: [prepare, build-source-package, build-debian-multiarch]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download source package
uses: actions/download-artifact@v4
with:
name: source-package
path: ./release-assets/
- name: Download Debian package
uses: actions/download-artifact@v4
with:
name: debian-multiarch
path: ./release-assets/
- name: Verify release assets
run: |
echo "Release assets:"
ls -lh release-assets/
echo ""
echo "Asset count: $(ls release-assets/ | wc -l)"
- name: Generate checksums
run: |
cd release-assets/
sha256sum * > SHA256SUMS
cat SHA256SUMS
cd ..
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: "zedit $"
generate_release_notes: true
draft: false
prerelease: false
files: release-assets/*
env:
GITHUB_TOKEN: $
Update CMakeLists.txt for Multiarch
1
2
3
4
5
6
7
# In CMakeLists.txt, update the Debian package configuration:
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "any") # For pure Python
# OR
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") # amd64 specific
# OR
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64") # arm64 specific
Current Recommendation: Option 2 (Multiarch)
For z-edit and z-open (pure Python packages):
Use Option 2 (Platform-Agnostic DEB) because:
- ✅ Simpler implementation
- ✅ Faster CI/CD builds
- ✅ Works on both amd64 and ARM64
- ✅ Follows Python packaging best practices
- ✅ No QEMU complexity
Implementation Steps
Step 1: Update debian/control (both projects)
1
Architecture: any
This declares the package as architecture-independent (pure Python).
Step 2: Update .github/workflows/release.yml
Use the multiarch template provided above.
Step 3: Update CMakeLists.txt
1
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "any")
Step 4: Test Build
1
2
3
cmake -B build -DZEDIT_BUILD_WHEEL=OFF
cmake --build build --target deb
dpkg -I build/zedit-*.deb | grep Architecture
Should show: Architecture: any
Verification
After implementation, release builds will:
- ✅ Create single .deb installable on both amd64 and ARM64
- ✅ Include Python scripts (architecture-independent)
- ✅ Work with
dpkg -ion any Debian-based system
Testing on ARM64
To verify the package works on ARM64:
1
2
3
# On an ARM64 system or emulator
sudo dpkg -i zedit-0.x.x-multiarch.deb
zedit --version
Future: True Cross-Architecture (if needed)
If you need separate amd64/ARM64 optimized binaries in the future:
- Use Dockerfile approach with QEMU
- Build in separate matrix jobs
- Upload both
*-amd64.deband*-arm64.deb - Mark each with explicit architecture
Summary
| Approach | Complexity | Build Time | Support |
|---|---|---|---|
| Option 1: Cross-compile | High | Slow | Both amd64 & ARM64 |
| Option 2: Multiarch | Low | Fast | Both amd64 & ARM64 |
| Current (amd64 only) | None | Fast | amd64 only |
Recommendation: Start with Option 2 (Multiarch). If performance optimization per architecture is needed later, migrate to Option 1.
Next: Apply to Both Projects
This solution applies to:
- ✅ z-edit
- ✅ z-open
Both are pure Python, so the same multiarch approach works perfectly.