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
204 changes: 198 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,25 @@ jobs:
- { os: ubuntu-24.04-arm, rid: linux-arm64 }
- { os: macos-14, rid: osx-x64 }
- { os: macos-14, rid: osx-arm64 }
# Android cross-compiles from the Linux image, which already carries
# an NDK; build-native.ps1 finds it through ANDROID_NDK_LATEST_HOME.
# Only the 64-bit ABIs are built: Google Play has required 64-bit for
# years, and armeabi-v7a would ship a slower scalar build, because
# upstream disables NEON on armv7 where it has no divide or sqrt.
- { os: ubuntu-latest, rid: android-arm64 }
- { os: ubuntu-latest, rid: android-x64 }

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

# The NDK toolchain only drives single-configuration generators, so the
# build needs Ninja. The Linux image has CMake but not Ninja.
- name: Install Ninja
if: startsWith(matrix.rid, 'android-')
run: sudo apt-get update && sudo apt-get install -y ninja-build

- name: Build Box3D
shell: pwsh
run: ./tools/build-native.ps1 -Rid ${{ matrix.rid }} -Configuration Release
Expand All @@ -55,6 +68,38 @@ jobs:
if-no-files-found: error
retention-days: 7

# iOS is built in one job rather than as three more rows above, because its
# three slices are not three independent outputs: they have to be merged into
# a single xcframework, and only a job holding all of them can do that. The
# merge is Apple tooling, so it cannot be deferred to the pack job on Linux
# either.
apple:
name: native (ios)
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Build every iOS slice
shell: pwsh
run: |
foreach ($rid in 'ios-arm64', 'iossimulator-arm64', 'iossimulator-x64') {
./tools/build-native.ps1 -Rid $rid -Configuration Release
}

- name: Assemble the xcframework
shell: pwsh
run: ./tools/create-xcframework.ps1

- uses: actions/upload-artifact@v4
with:
name: apple-xcframework
path: artifacts/apple/box3d.xcframework/
if-no-files-found: error
retention-days: 7

# Runs the managed test suite against the real native library. The layout and
# math tests would pass without one; the interop tests are the ones that
# actually prove the binding, so this job fails if any test is skipped.
Expand Down Expand Up @@ -251,26 +296,49 @@ jobs:

# Produces the packages that a release would publish, with every platform's
# native library inside, and keeps them as an artifact for inspection.
# On macOS, and not by preference: this is the only job that builds the
# packages' iOS assembly, and the iOS workload has no Linux host pack at all.
# "dotnet workload install ios" fails on ubuntu rather than installing
# something that cannot link, so the packaging job goes where the toolchain
# exists. Everything else it does is platform-independent.
pack:
name: pack
needs: [native, test, lint]
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [native, apple, test, lint]
runs-on: macos-14
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

# .NET 10 is here for the iOS target framework alone. The packages
# themselves still target net8.0 for every other platform, but the iOS
# assembly cannot be built by the .NET 8 SDK: its mobile workloads are out
# of support and the SDK refuses net8.0-ios outright.
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: |
8.0.x
10.0.x

# Only the iOS workload, and only to compile the managed assembly. Linking
# an actual application needs Xcode and happens in the consumer job; this
# produces the library half.
- name: Install the iOS workload
run: dotnet workload install ios --skip-sign-check

- name: Collect every native runtime
uses: actions/download-artifact@v4
with:
pattern: native-*
path: ./artifacts/native

- name: Collect the iOS framework
uses: actions/download-artifact@v4
with:
name: apple-xcframework
path: ./artifacts/apple/box3d.xcframework

# download-artifact places each artifact in its own folder named after
# the artifact, so native-win-x64/box3d.dll becomes
# runtimes/win-x64/native/box3d.dll.
Expand All @@ -295,15 +363,47 @@ jobs:
- name: Verify every runtime is present
shell: pwsh
run: |
$required = @('win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
$required = @(
'win-x64', 'win-arm64'
'linux-x64', 'linux-arm64'
'osx-x64', 'osx-arm64'
'android-arm64', 'android-x64'
)
$missing = $required | Where-Object { -not (Get-ChildItem "runtimes/$_/native" -File -ErrorAction SilentlyContinue) }
if ($missing) {
throw "No native library staged for: $($missing -join ', ')"
}
Write-Host "All $($required.Count) runtimes present."

# iOS is checked separately because it is not a runtime asset. The
# framework has to hold both variants: a device-only one links fine
# and then fails for everyone running on the simulator, which is most
# people most of the time.
$framework = './artifacts/apple/box3d.xcframework'
if (-not (Test-Path "$framework/Info.plist")) {
throw "No iOS xcframework was staged at $framework."
}

$variants = Get-ChildItem $framework -Directory | ForEach-Object { $_.Name }
Write-Host "xcframework variants: $($variants -join ', ')"

if (-not ($variants | Where-Object { $_ -notlike '*simulator*' })) {
throw 'The xcframework has no device variant.'
}
if (-not ($variants | Where-Object { $_ -like '*simulator*' })) {
throw 'The xcframework has no simulator variant.'
}

# Box3DTargetApple is what adds the iOS target framework to the packable
# projects. It is off by default so that building this repository needs
# neither the .NET 10 SDK nor a workload that does not exist for Linux;
# this is the job that asks for it, and the only one that has to.
- name: Pack
run: dotnet pack --configuration Release --output ./artifacts/packages
run: >
dotnet pack
--configuration Release
--output ./artifacts/packages
-p:Box3DTargetApple=true

# Inspect the package rather than trusting that pack did the right thing.
# A PackagePath that resolves to a directory rather than a file, for
Expand All @@ -320,6 +420,8 @@ jobs:
'runtimes/linux-arm64/native/libbox3d.so'
'runtimes/osx-x64/native/libbox3d.dylib'
'runtimes/osx-arm64/native/libbox3d.dylib'
'runtimes/android-arm64/native/libbox3d.so'
'runtimes/android-x64/native/libbox3d.so'
)

Add-Type -AssemblyName System.IO.Compression.FileSystem
Expand Down Expand Up @@ -356,6 +458,33 @@ jobs:
$failed = $true
}
}

# iOS travels as build files rather than runtime assets,
# so none of the checks above would notice it missing -
# and a package that restores on iOS and then cannot link
# is exactly the failure this whole job exists to catch.
if (-not ($entries | Where-Object { $_ -like 'buildTransitive/*/Box3D.NET.Native.targets' })) {
Write-Host "::error::$($package.Name) carries no iOS build file"
$failed = $true
}

foreach ($variant in 'ios-arm64', 'simulator') {
if (-not ($entries | Where-Object { $_ -like "buildTransitive/*/box3d.xcframework/*$variant*/libbox3d.a" })) {
Write-Host "::error::$($package.Name) is missing the $variant archive of box3d.xcframework"
$failed = $true
}
}

# The static archives belong to the framework and nowhere
# else. One under runtimes/ is dead weight: nothing loads
# an archive at run time, so it would add megabytes while
# serving no platform at all.
foreach ($entry in $entries) {
if ($entry -like 'runtimes/*.a' -or $entry -like 'runtimes/*/*.a') {
Write-Host "::error::$($package.Name) has a static archive under runtimes/: $entry"
$failed = $true
}
}
}

foreach ($expected in 'README.md', 'LICENSE', 'THIRD-PARTY-NOTICES.txt', 'icon.png') {
Expand Down Expand Up @@ -476,3 +605,66 @@ jobs:
if: matrix.aot
shell: pwsh
run: ./tools/verify-package.ps1 -Rid ${{ matrix.rid }} -Mode Aot

# The same idea as the job above, for the two platforms that have no
# executable a runner can start.
#
# Neither of these runs physics; they build a real application against the
# packed .nupkg and then open what it produced. That covers the part that is
# specific to mobile and invisible everywhere else: whether the native library
# is inside the apk, and whether the static archive survived the iOS link.
# What it does not cover is execution on a device, and the platform table says
# so rather than implying otherwise.
consumer-mobile:
name: package consumer (${{ matrix.platform }})
needs: pack
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, platform: Android, workload: android }
# A newer image than the jobs that only compile Box3D itself. Building
# the native library needs nothing but CMake and clang, which Xcode
# 15.4 on macos-14 provides; linking an iOS application is the .NET
# iOS workload's own job, and it refuses to run against an Xcode older
# than the one it was built for - 26.5 wants Xcode 26.6, and macos-14
# tops out at 15.4.
- { os: macos-26, platform: iOS, workload: ios }

steps:
- uses: actions/checkout@v4

# Reported, not chosen. macos-26 already defaults to Xcode 26.6, which is
# what this workload requires, and pointing xcode-select at the newest
# /Applications/Xcode_*.app instead broke the build: those side-by-side
# installs are not all complete, and the one picked had no macOS SDK, so
# actool could not run at all.
- name: Report the Xcode in use
if: matrix.platform == 'iOS'
run: xcodebuild -version

- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

# The Android SDK tooling pins the JDK it accepts and rejects anything
# newer outright (XA0030), so the runner's default cannot be relied on.
- uses: actions/setup-java@v4
if: matrix.platform == 'Android'
with:
distribution: temurin
java-version: '21'

- name: Install the ${{ matrix.workload }} workload
run: dotnet workload install ${{ matrix.workload }} --skip-sign-check

- uses: actions/download-artifact@v4
with:
name: packages
path: ./artifacts/packages

- name: Consume the package
shell: pwsh
run: ./tools/verify-package-mobile.ps1 -Platform ${{ matrix.platform }}
7 changes: 6 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ jobs:
- name: Restore tools
run: dotnet tool restore

# net8.0 explicitly, matching the TargetFramework docfx.json asks for.
# Without it this builds every framework the project declares, which now
# includes an iOS one, and would need the .NET 10 SDK and the iOS workload
# on a runner that has no use for either: the public API is the same on
# both sides, and the reference is generated from the net8.0 assembly.
- name: Build
run: dotnet build src/Box3D.NET/Box3D.NET.csproj --configuration Release
run: dotnet build src/Box3D.NET/Box3D.NET.csproj --configuration Release --framework net8.0

- name: Generate the icon
shell: pwsh
Expand Down
Loading
Loading