Skip to content

Commit cd6d09d

Browse files
committed
Build windows aarch64
1 parent e73e8ca commit cd6d09d

File tree

3 files changed

+138
-49
lines changed

3 files changed

+138
-49
lines changed

.github/workflows/build-ffmpeg.yml

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,28 @@ jobs:
2020
fail-fast: false
2121
matrix:
2222
include:
23-
- os: macos-14
24-
arch: arm64
25-
shell: bash
26-
- os: macos-15-intel
27-
arch: x86_64
28-
shell: bash
29-
- os: ubuntu-24.04-arm
30-
arch: aarch64
31-
shell: bash
32-
- os: ubuntu-24.04
33-
arch: x86_64
34-
shell: bash
23+
# - os: macos-14
24+
# arch: arm64
25+
# shell: bash
26+
# - os: macos-15-intel
27+
# arch: x86_64
28+
# shell: bash
29+
# - os: ubuntu-24.04-arm
30+
# arch: aarch64
31+
# shell: bash
32+
# - os: ubuntu-24.04
33+
# arch: x86_64
34+
# shell: bash
3535
- os: windows-latest
3636
arch: x86_64
3737
shell: 'msys2 {0}'
3838
msys_prefix: mingw-w64-x86_64
3939
msys_system: MINGW64
40+
- os: windows-11-arm
41+
arch: arm64
42+
shell: 'msys2 {0}'
43+
msys_prefix: mingw-w64-clang-aarch64
44+
msys_system: CLANGARM64
4045
defaults:
4146
run:
4247
shell: ${{ matrix.shell }}
@@ -62,14 +67,18 @@ jobs:
6267
brew install yasm
6368
fi
6469
- uses: msys2/setup-msys2@v2
65-
if: matrix.os == 'windows-latest'
70+
if: runner.os == 'Windows'
6671
with:
67-
install: base-devel openssl-devel ${{ matrix.msys_prefix }}-gcc ${{ matrix.msys_prefix }}-nasm
72+
install: >-
73+
base-devel
74+
openssl-devel
75+
${{ matrix.msys_prefix }}-pkgconf
76+
${{ matrix.msys_system == 'CLANGARM64' && format('{0}-clang', matrix.msys_prefix) || format('{0}-gcc {0}-nasm', matrix.msys_prefix) }}
6877
msystem: ${{ matrix.msys_system }}
6978
path-type: inherit
7079
- name: Build FFmpeg
7180
env:
72-
CIBW_ARCHS: ${{ matrix.msys_prefix && 'AMD64' || matrix.arch }}
81+
CIBW_ARCHS: ${{ matrix.msys_system == 'CLANGARM64' && 'ARM64' || (matrix.msys_prefix && 'AMD64' || matrix.arch) }}
7382
CIBW_BEFORE_BUILD: python scripts/build-ffmpeg.py /tmp/vendor
7483
CIBW_BEFORE_BUILD_WINDOWS: python scripts\build-ffmpeg.py C:\cibw\vendor
7584
CIBW_BUILD: cp311-*
@@ -91,7 +100,8 @@ jobs:
91100
strategy:
92101
fail-fast: false
93102
matrix:
94-
build: [ "manylinux_", "musllinux_" ]
103+
build: [ "musllinux_" ]
104+
# build: [ "manylinux_", "musllinux_" ]
95105
# arch: [ "loongarch64", "ppc64le", "riscv64", "s390x" ]
96106
arch: [ "ppc64le" ]
97107
steps:

scripts/build-ffmpeg.py

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ def calculate_sha256(filename: str) -> str:
9797
requires=["nasm"],
9898
build_system="meson",
9999
),
100-
Package(
101-
name="libsvtav1",
102-
source_url="https://gitlab.com/AOMediaCodec/SVT-AV1/-/archive/v3.1.2/SVT-AV1-v3.1.2.tar.bz2",
103-
sha256="802e9bb2b14f66e8c638f54857ccb84d3536144b0ae18b9f568bbf2314d2de88",
104-
build_system="cmake",
105-
build_arguments=["-DBUILD_APPS=OFF", "-DENABLE_NASM=ON"],
106-
),
100+
# Package(
101+
# name="libsvtav1",
102+
# source_url="https://gitlab.com/AOMediaCodec/SVT-AV1/-/archive/v3.1.2/SVT-AV1-v3.1.2.tar.bz2",
103+
# sha256="802e9bb2b14f66e8c638f54857ccb84d3536144b0ae18b9f568bbf2314d2de88",
104+
# build_system="cmake",
105+
# build_arguments=["-DBUILD_APPS=OFF", "-DENABLE_NASM=ON"],
106+
# ),
107107
Package(
108108
name="vpx",
109109
source_url="https://github.com/webmproject/libvpx/archive/refs/tags/v1.15.2.tar.gz",
@@ -148,12 +148,12 @@ def calculate_sha256(filename: str) -> str:
148148
source_filename="openh264-2.6.0.tar.gz",
149149
build_system="meson",
150150
),
151-
Package(
152-
name="opencore-amr",
153-
source_url="https://downloads.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-0.1.6.tar.gz",
154-
sha256="483eb4061088e2b34b358e47540b5d495a96cd468e361050fae615b1809dc4a1",
155-
build_arguments=["--disable-dependency-tracking"],
156-
),
151+
# Package(
152+
# name="opencore-amr",
153+
# source_url="https://downloads.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-0.1.6.tar.gz",
154+
# sha256="483eb4061088e2b34b358e47540b5d495a96cd468e361050fae615b1809dc4a1",
155+
# build_arguments=["--disable-dependency-tracking"],
156+
# ),
157157
Package(
158158
name="x264",
159159
source_url="https://code.videolan.org/videolan/x264/-/archive/b35605ace3ddf7c1a5d67a2eb553f034aef41d55/x264-b35605ace3ddf7c1a5d67a2eb553f034aef41d55.tar.bz2",
@@ -302,20 +302,28 @@ def main():
302302

303303
# install packages
304304
available_tools = set()
305+
machine = platform.machine().lower()
306+
is_arm = machine in {"arm64", "aarch64"}
305307
if plat == "Windows":
306-
available_tools.update(["nasm"])
308+
if not is_arm:
309+
available_tools.update(["nasm"])
307310

308311
# print tool locations
309312
print("PATH", os.environ["PATH"])
310-
for tool in ["gcc", "g++", "curl", "ld", "nasm", "pkg-config"]:
313+
if is_arm:
314+
# CLANGARM64 uses clang instead of gcc
315+
tools = ["clang", "clang++", "curl", "ld", "pkg-config"]
316+
else:
317+
tools = ["gcc", "g++", "curl", "ld", "nasm", "pkg-config"]
318+
for tool in tools:
311319
run(["where", tool])
312320

313321
with log_group("install python packages"):
314322
run(["pip", "install", "cmake==3.31.10", "meson", "ninja"])
315323

316324
# build tools
317325
build_tools = []
318-
if "nasm" not in available_tools and platform.machine() not in {"arm64", "aarch64"}:
326+
if "nasm" not in available_tools and platform.machine().lower() not in {"arm64", "aarch64"}:
319327
build_tools.append(
320328
Package(
321329
name="nasm",
@@ -344,12 +352,12 @@ def main():
344352
"--enable-gnutls" if use_gnutls else "--disable-gnutls",
345353
"--enable-libdav1d",
346354
"--enable-libmp3lame",
347-
"--enable-libopencore-amrnb",
348-
"--enable-libopencore-amrwb",
355+
# "--enable-libopencore-amrnb",
356+
# "--enable-libopencore-amrwb",
349357
"--enable-libopus",
350358
"--enable-libspeex",
351359
"--enable-libsvtav1",
352-
"--enable-libvorbis",
360+
# "--enable-libvorbis",
353361
"--enable-libvpx",
354362
"--enable-libwebp",
355363
"--enable-libopenh264",
@@ -412,6 +420,14 @@ def main():
412420
packages += codec_group
413421
packages += [ffmpeg_package]
414422

423+
# Disable runtime CPU detection for opus on Windows ARM64
424+
# (no CPU detection method available for this platform)
425+
if plat == "Windows" and is_arm:
426+
for pkg in packages:
427+
if pkg.name == "opus":
428+
pkg.build_arguments.append("--disable-rtcd")
429+
break
430+
415431
download_tars(build_tools + packages)
416432
for tool in build_tools:
417433
builder.build(tool, for_builder=True)
@@ -437,19 +453,33 @@ def main():
437453
)
438454

439455
# copy some libraries provided by mingw
456+
machine = platform.machine().lower()
457+
is_arm64 = machine in {"arm64", "aarch64"}
458+
compiler = "clang" if is_arm64 else "gcc"
440459
mingw_bindir = os.path.dirname(
441-
subprocess.run(["where", "gcc"], check=True, stdout=subprocess.PIPE)
460+
subprocess.run(["where", compiler], check=True, stdout=subprocess.PIPE)
442461
.stdout.decode()
443462
.splitlines()[0]
444463
.strip()
445464
)
446-
for name in (
447-
"libgcc_s_seh-1.dll",
448-
"libiconv-2.dll",
449-
"libstdc++-6.dll",
450-
"libwinpthread-1.dll",
451-
"zlib1.dll",
452-
):
465+
if is_arm64:
466+
# CLANGARM64 uses clang/libc++ instead of gcc/libstdc++
467+
dll_names = (
468+
"libc++.dll",
469+
"libiconv-2.dll",
470+
"libunwind.dll",
471+
"libwinpthread-1.dll",
472+
"zlib1.dll",
473+
)
474+
else:
475+
dll_names = (
476+
"libgcc_s_seh-1.dll",
477+
"libiconv-2.dll",
478+
"libstdc++-6.dll",
479+
"libwinpthread-1.dll",
480+
"zlib1.dll",
481+
)
482+
for name in dll_names:
453483
shutil.copy(os.path.join(mingw_bindir, name), os.path.join(dest_dir, "bin"))
454484

455485
# find libraries

scripts/cibuildpkg.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ def run(cmd: list[str], env=None) -> None:
7474
subprocess.run(cmd, check=True, env=env, stderr=subprocess.PIPE, text=True)
7575
except subprocess.CalledProcessError as e:
7676
print(f"stderr: {e.stderr}")
77+
# Print config.log tail if it exists (for ffmpeg configure debugging)
78+
config_log = os.path.join(os.getcwd(), "ffbuild", "config.log")
79+
if os.path.exists(config_log):
80+
print(f"\n=== Tail of {config_log} ===")
81+
with open(config_log, "r") as f:
82+
lines = f.readlines()
83+
print("".join(lines[-100:]))
7784
raise e
7885

7986

@@ -204,20 +211,31 @@ def _build_with_autoconf(self, package: Package, for_builder: bool) -> None:
204211
env = self._environment(for_builder=for_builder)
205212
prefix = self._prefix(for_builder=for_builder)
206213
configure_args = [
207-
"--disable-static",
208214
"--enable-shared",
209215
"--libdir=" + self._mangle_path(os.path.join(prefix, "lib")),
210216
"--prefix=" + self._mangle_path(prefix),
211217
]
212218

219+
if package.name == "x264":
220+
# Disable asm on Windows ARM64 (no nasm available)
221+
if platform.system() == "Windows" and platform.machine().lower() in {"arm64", "aarch64"}:
222+
configure_args.append("--disable-asm")
223+
# Specify host to ensure correct resource compiler target
224+
configure_args.append("--host=aarch64-w64-mingw32")
225+
213226
if package.name == "vpx":
214227
if platform.system() == "Darwin":
215228
if platform.machine() == "arm64":
216229
configure_args += ["--target=arm64-darwin20-gcc"]
217230
elif platform.machine() == "x86_64":
218231
configure_args += ["--target=x86_64-darwin20-gcc"]
219232
elif platform.system() == "Windows":
220-
configure_args += ["--target=x86_64-win64-gcc"]
233+
if platform.machine().lower() in {"arm64", "aarch64"}:
234+
configure_args += ["--target=arm64-win64-gcc"]
235+
# Link pthread for ARM64 Windows
236+
prepend_env(env, "LDFLAGS", "-lpthread")
237+
else:
238+
configure_args += ["--target=x86_64-win64-gcc"]
221239
elif platform.system() == "Linux":
222240
if "RUNNER_ARCH" in os.environ:
223241
prepend_env(env, "CFLAGS", "-pthread")
@@ -229,9 +247,24 @@ def _build_with_autoconf(self, package: Package, for_builder: bool) -> None:
229247
prepend_env(
230248
env,
231249
"PKG_CONFIG_PATH",
232-
"/c/msys64/usr/lib/pkgconfig",
233-
separator=":",
250+
"C:/msys64/usr/lib/pkgconfig",
251+
separator=";",
252+
)
253+
# Debug: print pkg-config info
254+
print(f"PKG_CONFIG_PATH: {env.get('PKG_CONFIG_PATH')}")
255+
print(f"PKG_CONFIG: {env.get('PKG_CONFIG')}")
256+
import glob
257+
pc_files = glob.glob(os.path.join(prefix, "lib", "pkgconfig", "*.pc"))
258+
print(f"PC files in {prefix}/lib/pkgconfig: {pc_files}")
259+
# Test pkgconf directly
260+
import subprocess
261+
result = subprocess.run(
262+
["pkgconf", "--modversion", "dav1d"],
263+
env=env,
264+
capture_output=True,
265+
text=True
234266
)
267+
print(f"pkgconf dav1d test: returncode={result.returncode}, stdout={result.stdout}, stderr={result.stderr}")
235268

236269
# build package
237270
os.makedirs(package_build_path, exist_ok=True)
@@ -419,18 +452,34 @@ def _environment(self, *, for_builder: bool) -> dict[str, str]:
419452
prepend_env(
420453
env, "LDFLAGS", "-L" + self._mangle_path(os.path.join(prefix, "lib"))
421454
)
455+
# Use ; as separator on Windows, : on Unix
456+
# Don't mangle PKG_CONFIG_PATH on Windows - pkgconf expects native paths
457+
pkg_config_sep = ";" if platform.system() == "Windows" else ":"
458+
pkg_config_path = os.path.join(prefix, "lib", "pkgconfig")
459+
if platform.system() != "Windows":
460+
pkg_config_path = self._mangle_path(pkg_config_path)
422461
prepend_env(
423462
env,
424463
"PKG_CONFIG_PATH",
425-
self._mangle_path(os.path.join(prefix, "lib", "pkgconfig")),
426-
separator=":",
464+
pkg_config_path,
465+
separator=pkg_config_sep,
427466
)
428467

429468
if platform.system() == "Darwin" and not for_builder:
430469
arch_flags = os.environ["ARCHFLAGS"]
431470
for var in ["CFLAGS", "CXXFLAGS", "LDFLAGS"]:
432471
prepend_env(env, var, arch_flags)
433472

473+
# Use clang on Windows ARM64 (CLANGARM64 environment)
474+
if platform.system() == "Windows" and platform.machine().lower() in {"arm64", "aarch64"}:
475+
env["CC"] = "clang"
476+
env["CXX"] = "clang++"
477+
# Use LLVM windres to compile Windows resources for ARM64
478+
env["RC"] = "llvm-windres"
479+
env["WINDRES"] = "llvm-windres"
480+
# Use MinGW pkgconf instead of MSYS pkg-config
481+
env["PKG_CONFIG"] = "pkgconf"
482+
434483
return env
435484

436485
def _mangle_path(self, path: str) -> str:

0 commit comments

Comments
 (0)