Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,73 @@ jobs:
pushd build
cmake --build . --target install -- VERBOSE=1
popd

windows-arm64:
name: Windows ARM64 build and tests
runs-on: windows-11-arm
steps:
- name: Preserve repository line endings
run: git config --global core.autocrlf input

- uses: actions/checkout@v4

- uses: msys2/setup-msys2@v2
with:
msystem: CLANGARM64
# Use available rolling packages; record versions against the baseline.
# An archived package snapshot is required for reproducible builds.
update: true

- name: Install CLANGARM64 packages
shell: msys2 {0}
run: |
mapfile -t packages < <(sed -n '/^[^#[:space:]][^=]*=/s/=.*//p' ci/windows-arm64-packages.lock)
pacman --noconfirm -S --needed -- "${packages[@]}"

- name: Build libopenshot-audio (CLANGARM64, ARM64)
shell: msys2 {0}
run: |
cmake -B build -S . -G Ninja \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_INSTALL_PREFIX="$PWD/dist-arm64" \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_AUDIO_DOCS=OFF
cmake --build build
cmake --install build
cmake -S ci/audio-smoke -B smoke-build -G Ninja \
-DCMAKE_PREFIX_PATH="$PWD/dist-arm64" -DREQUIRE_ARM64=ON
cmake --build smoke-build
PATH="$PWD/dist-arm64/bin:$PATH" ctest --test-dir smoke-build --output-on-failure --no-tests=error
python -m unittest discover -s ci -p "test_*.py" -v
python ci/validate_arm64_architecture.py \
--require-native-arm64 \
--package-baseline ci/windows-arm64-packages.lock \
--payload-root dist-arm64 \
--require-payload \
--json-report build/arm64-report.json

linux-arm64:
name: Linux ARM64 build and tests
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build libasound2-dev python3
- name: Build and install audio library
run: |
test "$(uname -m)" = aarch64
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$PWD/install-arm64" -DENABLE_AUDIO_DOCS=OFF
cmake --build build --parallel 2
cmake --install build
readelf -h install-arm64/lib/libopenshot-audio.so | grep -Eq 'Machine:.*AArch64'
- name: Test installed audio library and validator
run: |
cmake -S ci/audio-smoke -B smoke-build -G Ninja \
-DCMAKE_PREFIX_PATH="$PWD/install-arm64" -DREQUIRE_ARM64=ON
cmake --build smoke-build
ctest --test-dir smoke-build --output-on-failure --no-tests=error
python3 -m unittest discover -s ci -p 'test_*.py' -v
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
.settings
.idea/*
cmake-build-debug/*

# Python validator bytecode
__pycache__/
42 changes: 42 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,48 @@ windows-builder-x64:
- tags
tags: [windows]

# WINDOWS ARM64 (native, additive lane; existing x64/x86 jobs are unchanged).
# Contract identity: windows-arm64-clangarm64-v1 (design-spec.md).
# Verifies the shared ci/windows-arm64-packages.lock and validates every
# produced .exe/.dll/.pyd, plus the native-process oracle of the build host,
# using ci/validate_arm64_architecture.py (design-amendment-A1 semantics:
# native execution requires pProcessMachine == IMAGE_FILE_MACHINE_UNKNOWN
# and pNativeMachine == IMAGE_FILE_MACHINE_ARM64; every payload PE must be
# machine 0xAA64 independently).
windows-builder-arm64:
stage: build-libopenshot-audio
artifacts:
expire_in: 6 months
paths:
- build/install-arm64/
- build\arm64-architecture-report.json
script:
- $env:Path = "C:\msys64\clangarm64\bin;C:\msys64\usr\bin;" + $env:Path;
- $env:MSYSTEM = "CLANGARM64"
- New-Item -ItemType Directory -Force -Path build
- cmake -B build -S . -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"CMAKE_INSTALL_PREFIX:PATH=build/install-arm64" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G Ninja -D"CMAKE_BUILD_TYPE:STRING=Release"
- cmake --build build
- ctest --test-dir build --output-on-failure -VV
- cmake --install build
- python -m unittest discover -s ci -p "test_*.py" -v
- python ci\validate_arm64_architecture.py --require-native-arm64 --package-baseline ci\windows-arm64-packages.lock --payload-root build\install-arm64 --require-payload --json-report build\arm64-architecture-report.json
- $PROJECT_VERSION = (Select-String -Path "CMakeLists.txt" -Pattern '^set\(PROJECT_VERSION_FULL "(.*)\"' | %{$_.Matches.Groups[1].value})
- $PROJECT_SO = (Select-String -Path "CMakeLists.txt" -Pattern '^set\(PROJECT_SO_VERSION (.*)\)' | %{$_.Matches.Groups[1].value})
- New-Item -path "build/install-arm64/share/" -Name "$CI_PROJECT_NAME.env" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID`nCI_PIPELINE_ID:$CI_PIPELINE_ID`nVERSION:$PROJECT_VERSION`nSO:$PROJECT_SO`nTARGET_TRIPLET:aarch64-w64-mingw32`nPE_MACHINE:0xAA64" -ItemType file -force
- $PREV_GIT_LABEL=(git describe --tags --abbrev=0 '@^')
- git log "$PREV_GIT_LABEL..@" --oneline --pretty=format:"- %C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "build/install-arm64/share/$CI_PROJECT_NAME.log"
only:
variables:
- $ENABLE_WINDOWS_ARM64 == "1"
except:
- tags
tags: [windows-arm64]
# Requires release infrastructure's native/virtual Windows Arm64 GitLab
# runner tagged windows-arm64 (design-spec.md "Release-infrastructure
# surface"). Until that runner exists, this job cannot execute; it does
# not weaken or replace the existing windows-builder-x64/x86 jobs.
allow_failure: true

windows-builder-x86:
stage: build-libopenshot-audio
artifacts:
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ if(WIN32)
if(TARGET ASIO::SDK)
target_link_libraries(openshot-audio PRIVATE ASIO::SDK)
set(NEED_ASIO TRUE)
else()
target_compile_definitions(openshot-audio PUBLIC JUCE_ASIO=0)
endif()
# Order here can be important!
# For example, winmm.lib must come before kernel32.lib (if linked)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1193,7 +1193,7 @@ class AudioBuffer
jassert (size >= 0);

auto channelListSize = (size_t) (numChannels + 1) * sizeof (Type*);
auto requiredSampleAlignment = std::alignment_of_v<Type>;
auto requiredSampleAlignment = alignof (Type);
size_t alignmentOverflow = channelListSize % requiredSampleAlignment;

if (alignmentOverflow != 0)
Expand Down
34 changes: 34 additions & 0 deletions ci/README-arm64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--
SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
SPDX-License-Identifier: LGPL-3.0-or-later
-->

# ARM64 build checks

The `arm64-support` branch collects build support before integration into
`develop`. Existing Linux, Intel Mac, and Windows x86/x64 jobs remain enabled.

GitHub automatically runs **Linux ARM64 build and tests** on
`ubuntu-24.04-arm` and **Windows ARM64 build and tests** on `windows-11-arm`
for pushes and pull requests. No repository enable variables are needed, and
failures are reported normally. These checks do not publish releases.

The production GitLab configuration is unchanged: its optional Windows ARM64
job still requires `ENABLE_WINDOWS_ARM64=1`, a runner tagged `windows-arm64`,
and MSYS2 CLANGARM64 installed at `C:\msys64`.

`windows-arm64-packages.lock` is a reference package list. GitHub installs
currently available packages by name, and the validator records actual
versions and reports deviations using `--package-baseline`. GitLab expects
the packages to be provisioned on the runner. Reproducible builds require a
separately archived package snapshot; `--package-lock` retains strict version
checking for such environments. Archive checksums are not verified.

Native validation requires both the Windows host API result and an ARM64 PE
header on the running Python interpreter. Every produced executable, DLL,
and Python extension is also checked independently. A Linux validator test
pass is not evidence of native Windows execution.

The audio checks build a consumer of the installed library, apply gain to
stereo samples, and verify a WAV encode/decode round trip without requiring
an audio device. They also run the architecture-validator unit tests.
15 changes: 15 additions & 0 deletions ci/audio-smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
# SPDX-License-Identifier: LGPL-3.0-or-later

cmake_minimum_required(VERSION 3.16)
project(AudioSmokeTest LANGUAGES CXX)
find_package(OpenShotAudio REQUIRED)
option(REQUIRE_ARM64 "Require a native ARM64 test executable" OFF)
add_executable(audio-smoke main.cpp)
target_compile_features(audio-smoke PRIVATE cxx_std_17)
target_link_libraries(audio-smoke PRIVATE OpenShot::Audio)
if(REQUIRE_ARM64)
target_compile_definitions(audio-smoke PRIVATE REQUIRE_ARM64=1)
endif()
enable_testing()
add_test(NAME audio-buffer-roundtrip COMMAND audio-smoke)
43 changes: 43 additions & 0 deletions ci/audio-smoke/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
// SPDX-License-Identifier: LGPL-3.0-or-later

#include <cmath>
#include <iostream>
#include <memory>
#include "OpenShotAudio.h"

#if defined(REQUIRE_ARM64) && !defined(__aarch64__) && !defined(_M_ARM64)
#error The ARM64 smoke test must be compiled for ARM64
#endif

int main()
{
// Exercise the installed library without requiring an audio device.
juce::AudioBuffer<float> samples(2, 128);
for (int i = 0; i < samples.getNumSamples(); ++i) {
samples.setSample(0, i, 0.5f);
samples.setSample(1, i, -0.25f);
}
samples.applyGain(0.5f);
juce::MemoryBlock encoded;
juce::WavAudioFormat format;
auto output = std::make_unique<juce::MemoryOutputStream>(encoded, false);
std::unique_ptr<juce::AudioFormatWriter> writer(
format.createWriterFor(output.get(), 48000, 2, 16, {}, 0));
if (!writer) return 1;
output.release(); // The writer owns the stream after successful creation.
if (!writer->writeFromAudioSampleBuffer(samples, 0, samples.getNumSamples())) return 1;
writer.reset(); // Finalize the WAV header before reading it.
std::unique_ptr<juce::AudioFormatReader> reader(format.createReaderFor(
new juce::MemoryInputStream(encoded, false), true));
if (!reader || reader->numChannels != 2 || reader->sampleRate != 48000
|| reader->lengthInSamples != 128) return 1;
juce::AudioBuffer<float> decoded(2, 128);
if (!reader->read(&decoded, 0, 128, 0, true, true)) return 1;
for (int i = 0; i < decoded.getNumSamples(); ++i) {
if (std::abs(decoded.getSample(0, i) - 0.25f) > 0.0001f
|| std::abs(decoded.getSample(1, i) + 0.125f) > 0.0001f) return 1;
}
std::cout << "Installed audio library: buffer gain and WAV round trip passed\n";
return 0;
}
Loading
Loading