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
115 changes: 112 additions & 3 deletions .github/workflows/package-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
# produced, drops a sphere onto a floor through the native library, and fails the job if the
# sphere does not fall, does not settle, or falls through.
#
# Six of the ten identifiers, and the gap is honest rather than an oversight: android-arm,
# android-arm64, ios-arm64 and iossimulator-arm64 ship in the package and no GitHub runner can
# execute a console application on them. They are built and never run.
# Eight of the ten identifiers. ios-arm64 and iossimulator-arm64 cannot be *run* on a runner,
# but the simulator is linked and its executable inspected; android cannot be run either, but an
# APK is built against the package and opened, and both libJoltC.so have to be inside it. What
# remains unexecuted anywhere is ios-arm64 (a device build needs a signing identity) and the
# android libraries themselves, which is stated in the README rather than glossed.
#
# browser-wasm is the sixth and it needs its own job rather than a row in the matrix, because
# nothing about it resembles the others: the archive is linked into the application when it
Expand Down Expand Up @@ -346,3 +348,110 @@ jobs:
fi
echo " ok JoltC_Init is defined (T) in the executable"
echo "PASS"

# Builds an APK against the package and then opens it. `dotnet build` succeeding proves the
# managed side compiled and says nothing about whether the native libraries travelled with it,
# which is the only question this leg exists to answer.
#
# One APK, both ABIs. Unlike Cesium.NET's equivalent, this package ships android-arm and
# android-arm64, so the project multi-targets both runtime identifiers and a single build
# verifies both libraries reached the APK.
android:
name: android
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

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

- name: Install the android workload
run: dotnet workload install android

- name: Fetch the package from this run
if: inputs.cd-run-id == ''
uses: actions/download-artifact@v4
with:
name: nuget-packages
path: AndroidSmokeTest/local-packages

- name: Fetch the package from an earlier run
if: inputs.cd-run-id != ''
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir -p AndroidSmokeTest/local-packages
gh run download "${{ inputs.cd-run-id }}" --repo "$GITHUB_REPOSITORY" -n nuget-packages -D AndroidSmokeTest/local-packages

- name: Work out which version is under test
shell: bash
run: |
set -euo pipefail
ls -la AndroidSmokeTest/local-packages/

pkg=$(ls AndroidSmokeTest/local-packages/*.nupkg | head -1)
version=$(basename "$pkg" .nupkg | sed 's/^Evergine\.Bindings\.JoltPhysics\.//')
if [ -z "$version" ] || [ "$version" = "$(basename "$pkg" .nupkg)" ]; then
echo "::error::could not read a version out of '$(basename "$pkg")'"
exit 1
fi
echo "JOLT_VERSION=$version" >> "$GITHUB_ENV"
echo "testing $version on android"

- name: Build an APK against the package
shell: bash
working-directory: AndroidSmokeTest
run: |
set -euo pipefail
dotnet build -c Release -p:JoltPhysicsVersion="$JOLT_VERSION"

# The assertion. A package can carry runtimes/android-*/native and still lose them on the
# way into the APK, and the build would be green either way.
- name: Check both native libraries are inside the APK
shell: bash
working-directory: AndroidSmokeTest
run: |
set -euo pipefail
apk=$(find bin -name '*-Signed.apk' | head -1)
[ -n "$apk" ] || { echo "::error::no APK was produced"; exit 1; }

echo "APK: $apk"
python3 - "$apk" <<'CHECK'
import sys, zipfile, os

apk = sys.argv[1]
z = zipfile.ZipFile(apk)
wanted = ["lib/armeabi-v7a/libJoltC.so", "lib/arm64-v8a/libJoltC.so"]

libs = sorted(n for n in z.namelist() if n.startswith("lib/"))
print(" APK is %.1f MB and carries %d native libraries" % (os.path.getsize(apk) / 1e6, len(libs)))

bad = 0
for w in wanted:
if w not in libs:
print("::error::%s is not in the APK. The package carried it and the build dropped it." % w)
bad += 1
continue
size = z.getinfo(w).file_size
# A library that is present and empty would satisfy presence and nothing else.
if size < 1_000_000:
print("::error::%s is only %d bytes; that is not JoltC." % (w, size))
bad += 1
continue
print(" ok %s (%.1f MB uncompressed)" % (w, size / 1e6))

# Every ABI here is one the package had to supply. Anything else means the project
# asked for a runtime identifier JoltPhysicsC does not build.
abis = {n.split("/")[1] for n in libs}
if abis != {"armeabi-v7a", "arm64-v8a"}:
print("::error::unexpected ABIs in the APK: %s" % ", ".join(sorted(abis)))
bad += 1

if bad:
sys.exit(1)
print("PASS")
CHECK
34 changes: 34 additions & 0 deletions AndroidSmokeTest/AndroidSmokeTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<OutputType>Exe</OutputType>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationId>com.evergine.joltsmoketest</ApplicationId>
<ApplicationVersion>1</ApplicationVersion>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>

<!--
Both ABIs the package ships, and this is where this differs from Cesium's equivalent,
which limits to one. JoltPhysicsC builds android-arm and android-arm64, so one APK carries
lib/armeabi-v7a and lib/arm64-v8a and the CI leg verifies both .so travelled in a single
build. Left to itself the Android SDK would also target x86 flavours the package has no
library for, and the packaging step would fail for a reason that has nothing to do with
what is under test.
-->
<RuntimeIdentifiers>android-arm;android-arm64</RuntimeIdentifiers>
</PropertyGroup>

<!--
A PackageReference on purpose, not a ProjectReference. What is under test is the package:
whether it carries a library for each Android identifier and whether the Android SDK finds
them there and puts them in the APK. A project reference bypasses the runtimes/ layout
entirely and would pass against a package shipping nothing at all.
-->
<ItemGroup>
<PackageReference Include="Evergine.Bindings.JoltPhysics" Version="$(JoltPhysicsVersion)" />
</ItemGroup>

</Project>
48 changes: 48 additions & 0 deletions AndroidSmokeTest/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Exists so the package has to be consumed by something that really builds for Android.
//
// It is not run. No GitHub runner can execute an APK without an emulator, and standing one up
// per release was decided against. What the CI leg asserts instead is that libJoltC.so ends up
// inside the APK under lib/armeabi-v7a/ and lib/arm64-v8a/ -- which is the class of mistake
// this package actually shipped for months on wasm and iOS: a native library present in the
// package and absent from where the platform looks for it. A build that merely compiles would
// not notice either.
//
// The calls below are here so the binding assembly is genuinely referenced rather than trimmed
// away as unused. They run on a device and never in CI.

using Android.App;
using Android.OS;
using Android.Widget;
using Evergine.Bindings.JoltPhysics;

namespace AndroidSmokeTest;

[Activity(Label = "Jolt smoke", MainLauncher = true)]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);

// The same first calls the desktop leg makes. Reaching them at all means the native
// library was found and loaded out of the APK.
JoltPhysics.RegisterDefaultAllocator();
JoltPhysics.Init();
JoltPhysics.CreateFactory();
JoltPhysics.RegisterTypes();

System.IntPtr tempAllocator = JoltPhysics.TempAllocator_Create(4 * 1024 * 1024);
System.IntPtr jobSystem = JoltPhysics.JobSystemThreadPool_Create(1024, 8, 1);

bool ok = tempAllocator != System.IntPtr.Zero && jobSystem != System.IntPtr.Zero;

var text = new TextView(this)
{
Text = ok
? $"JoltC loaded: allocator=0x{tempAllocator:x} jobs=0x{jobSystem:x}"
: "JoltC did not load",
};

SetContentView(text);
}
}
8 changes: 8 additions & 0 deletions AndroidSmokeTest/NuGet.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local" value="local-packages" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This repository contains low-level bindings for [JoltPhysics](https://github.com
- [x] iOS Simulator ARM64
- [x] Browser WASM

Ten runtime identifiers ship. Seven of them are checked against the real `.nupkg` before a
Ten runtime identifiers ship. Nine of them are checked against the real `.nupkg` before a
release is published, and a failure stops the publish. What that check is worth differs, and the
difference is worth knowing rather than glossing:

Expand All @@ -41,7 +41,7 @@ difference is worth knowing rather than glossing:
| `browser-wasm` | the same, under node, against the archive linked into the application at publish time |
| `iossimulator-arm64` | an application is linked against the package and its executable has to **define** the entry points, not merely reference them |
| `ios-arm64` | **not verified directly.** A device build needs a signing identity CI does not have. It links the same archive through the same targets file as the simulator, so the evidence is indirect |
| `android-arm`, `android-arm64` | **not verified.** They are ordinary shared libraries loaded the way the desktop ones are, but nothing installs the package on Android today |
| `android-arm`, `android-arm64` | an APK is built against the package and opened, and both `libJoltC.so` have to be inside it, over a megabyte each, with no other ABI alongside. The libraries themselves are not executed |

iOS works differently from every other identifier and you do not have to do anything about it:
Apple only lets an application load dynamic libraries that ship inside its own bundle, so the
Expand Down
Loading