Skip to content

Commit 4def3ec

Browse files
authored
close framework gaps: tasks 1–6 (dsp fixture, CI, docs, distribution, NativeAOT)
1 parent e3ddc2c commit 4def3ec

8 files changed

Lines changed: 241 additions & 45 deletions

File tree

.github/workflows/codegen-tests.yml

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ on:
99
- 'tools/codegen/**'
1010
- 'src/Framework/**'
1111
- 'src/Framework.Tests/**'
12+
- 'native/test_stub/**'
13+
- 'native/dsp_fixture/**'
1214
- '.github/workflows/codegen-tests.yml'
1315
- 'Directory.Build.props'
1416
pull_request:
1517
paths:
1618
- 'tools/codegen/**'
1719
- 'src/Framework/**'
1820
- 'src/Framework.Tests/**'
21+
- 'native/test_stub/**'
22+
- 'native/dsp_fixture/**'
1923
- '.github/workflows/codegen-tests.yml'
2024
- 'Directory.Build.props'
2125

@@ -81,7 +85,7 @@ jobs:
8185
- name: Build codegen
8286
run: FerrumSkipMobile=true dotnet build tools/codegen/Ferrum.Codegen.csproj -c Release
8387

84-
- name: Run codegen on test stub header
88+
- name: Run codegen on test stub header (ferrum_add)
8589
run: |
8690
FerrumSkipMobile=true dotnet run --project tools/codegen/Ferrum.Codegen.csproj \
8791
--configuration Release \
@@ -92,11 +96,32 @@ jobs:
9296
--class AddBindings \
9397
--lib __Internal
9498
95-
- name: Verify output
99+
- name: Verify add.h output
96100
run: |
97-
echo "=== Generated bindings ==="
101+
echo "=== Generated bindings (add.h) ==="
98102
cat /tmp/AddBindings.cs
99-
# Ensure [LibraryImport] and the function name are present
100103
grep -q "\[LibraryImport" /tmp/AddBindings.cs
101104
grep -q "ferrum_add" /tmp/AddBindings.cs
102-
echo "==> Smoke test passed"
105+
echo "==> add.h smoke test passed"
106+
107+
- name: Run codegen on DSP fixture header (ferrum_dsp)
108+
run: |
109+
FerrumSkipMobile=true dotnet run --project tools/codegen/Ferrum.Codegen.csproj \
110+
--configuration Release \
111+
-- \
112+
--input native/dsp_fixture/include/ferrum_dsp.h \
113+
--output /tmp/DspBindings.cs \
114+
--ns Ferrum.Generated \
115+
--class DspBindings \
116+
--lib __Internal
117+
118+
- name: Verify ferrum_dsp.h output
119+
run: |
120+
echo "=== Generated bindings (ferrum_dsp.h) ==="
121+
cat /tmp/DspBindings.cs
122+
grep -q "\[LibraryImport" /tmp/DspBindings.cs
123+
grep -q "ferrum_dsp_scale" /tmp/DspBindings.cs
124+
grep -q "ferrum_dsp_stats" /tmp/DspBindings.cs
125+
grep -q "FerrumDspStats" /tmp/DspBindings.cs
126+
grep -q "StructLayout" /tmp/DspBindings.cs
127+
echo "==> ferrum_dsp.h smoke test passed"

.github/workflows/native-build.yml

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,37 @@ permissions:
2222
jobs:
2323
# ── iOS XCFramework ────────────────────────────────────────────────────────
2424
ios:
25-
name: iOS XCFramework (${{ matrix.platform }})
25+
name: iOS XCFramework (${{ matrix.platform }}, ${{ matrix.target }})
2626
runs-on: macos-15
2727
strategy:
2828
matrix:
2929
include:
30+
# test_stub slices
3031
- platform: OS64
3132
sysroot: iphoneos
3233
label: device-arm64
34+
target: ferrum_test_stub
3335
- platform: SIMULATORARM64
3436
sysroot: iphonesimulator
3537
label: sim-arm64
38+
target: ferrum_test_stub
3639
- platform: SIMULATOR64
3740
sysroot: iphonesimulator
3841
label: sim-x86_64
42+
target: ferrum_test_stub
43+
# dsp_fixture slices
44+
- platform: OS64
45+
sysroot: iphoneos
46+
label: dsp-device-arm64
47+
target: ferrum_dsp_fixture
48+
- platform: SIMULATORARM64
49+
sysroot: iphonesimulator
50+
label: dsp-sim-arm64
51+
target: ferrum_dsp_fixture
52+
- platform: SIMULATOR64
53+
sysroot: iphonesimulator
54+
label: dsp-sim-x86_64
55+
target: ferrum_dsp_fixture
3956

4057
steps:
4158
- uses: actions/checkout@v4
@@ -57,65 +74,78 @@ jobs:
5774
run: |
5875
cmake --build .build/ios/${{ matrix.label }} \
5976
--config Release \
60-
--target ferrum_test_stub
77+
--target ${{ matrix.target }}
6178
6279
- name: Upload slice artifact
6380
uses: actions/upload-artifact@v4
6481
with:
6582
name: ios-${{ matrix.label }}
66-
path: .build/ios/${{ matrix.label }}/Release-${{ matrix.sysroot }}/libferrum_test_stub.a
83+
path: .build/ios/${{ matrix.label }}/Release-${{ matrix.sysroot }}/lib${{ matrix.target }}.a
6784

68-
# Assemble the full XCFramework from the three slices
85+
# Assemble the full XCFramework from the three slices (per target)
6986
ios-xcframework:
70-
name: Assemble XCFramework
87+
name: Assemble XCFramework (${{ matrix.target }})
7188
runs-on: macos-15
7289
needs: ios
90+
strategy:
91+
matrix:
92+
include:
93+
- target: ferrum_test_stub
94+
device_label: device-arm64
95+
sim_arm64_label: sim-arm64
96+
sim_x86_label: sim-x86_64
97+
- target: ferrum_dsp_fixture
98+
device_label: dsp-device-arm64
99+
sim_arm64_label: dsp-sim-arm64
100+
sim_x86_label: dsp-sim-x86_64
73101
steps:
74102
- uses: actions/checkout@v4
75103

76104
- uses: actions/download-artifact@v4
77105
with:
78-
name: ios-device-arm64
79-
path: .slices/device
106+
name: ios-${{ matrix.device_label }}
107+
path: .slices/${{ matrix.target }}/device
80108

81109
- uses: actions/download-artifact@v4
82110
with:
83-
name: ios-sim-arm64
84-
path: .slices/sim-arm64
111+
name: ios-${{ matrix.sim_arm64_label }}
112+
path: .slices/${{ matrix.target }}/sim-arm64
85113

86114
- uses: actions/download-artifact@v4
87115
with:
88-
name: ios-sim-x86_64
89-
path: .slices/sim-x86_64
116+
name: ios-${{ matrix.sim_x86_label }}
117+
path: .slices/${{ matrix.target }}/sim-x86_64
90118

91119
- name: Create fat simulator library
92120
run: |
121+
mkdir -p .slices/${{ matrix.target }}/sim-fat
93122
lipo -create \
94-
.slices/sim-arm64/libferrum_test_stub.a \
95-
.slices/sim-x86_64/libferrum_test_stub.a \
96-
-output .slices/sim-fat/libferrum_test_stub.a
123+
.slices/${{ matrix.target }}/sim-arm64/lib${{ matrix.target }}.a \
124+
.slices/${{ matrix.target }}/sim-x86_64/lib${{ matrix.target }}.a \
125+
-output .slices/${{ matrix.target }}/sim-fat/lib${{ matrix.target }}.a
97126
98127
- name: Create XCFramework
99128
run: |
100129
mkdir -p artifacts/ios
101130
xcodebuild -create-xcframework \
102-
-library .slices/device/libferrum_test_stub.a \
103-
-library .slices/sim-fat/libferrum_test_stub.a \
104-
-output artifacts/ios/libferrum_test_stub.xcframework
131+
-library .slices/${{ matrix.target }}/device/lib${{ matrix.target }}.a \
132+
-library .slices/${{ matrix.target }}/sim-fat/lib${{ matrix.target }}.a \
133+
-output artifacts/ios/lib${{ matrix.target }}.xcframework
105134
106135
- name: Upload XCFramework
107136
uses: actions/upload-artifact@v4
108137
with:
109-
name: ios-xcframework
110-
path: artifacts/ios/libferrum_test_stub.xcframework
138+
name: ios-xcframework-${{ matrix.target }}
139+
path: artifacts/ios/lib${{ matrix.target }}.xcframework
111140

112141
# ── Android .so per ABI ────────────────────────────────────────────────────
113142
android:
114-
name: Android (${{ matrix.abi }})
143+
name: Android (${{ matrix.abi }}, ${{ matrix.target }})
115144
runs-on: ubuntu-24.04
116145
strategy:
117146
matrix:
118147
abi: [arm64-v8a, armeabi-v7a, x86_64, x86]
148+
target: [ferrum_test_stub, ferrum_dsp_fixture]
119149

120150
steps:
121151
- uses: actions/checkout@v4
@@ -130,20 +160,20 @@ jobs:
130160
run: |
131161
NDK_ROOT="${ANDROID_SDK_ROOT}/ndk/27.2.12479018"
132162
cmake -S native \
133-
-B .build/android/${{ matrix.abi }} \
163+
-B .build/android/${{ matrix.abi }}-${{ matrix.target }} \
134164
-DCMAKE_TOOLCHAIN_FILE="${NDK_ROOT}/build/cmake/android.toolchain.cmake" \
135165
-DANDROID_ABI=${{ matrix.abi }} \
136166
-DANDROID_PLATFORM=android-24 \
137167
-DCMAKE_BUILD_TYPE=Release
138168
139169
- name: Build
140170
run: |
141-
cmake --build .build/android/${{ matrix.abi }} \
171+
cmake --build .build/android/${{ matrix.abi }}-${{ matrix.target }} \
142172
--config Release \
143-
--target ferrum_test_stub
173+
--target ${{ matrix.target }}
144174
145175
- name: Upload .so artifact
146176
uses: actions/upload-artifact@v4
147177
with:
148-
name: android-${{ matrix.abi }}
149-
path: .build/android/${{ matrix.abi }}/libferrum_test_stub.so
178+
name: android-${{ matrix.abi }}-${{ matrix.target }}
179+
path: .build/android/${{ matrix.abi }}-${{ matrix.target }}/lib${{ matrix.target }}.so

Ferrum.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{A0000002
99
EndProject
1010
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{A0000003-0000-0000-0000-000000000003}"
1111
EndProject
12+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{A0000004-0000-0000-0000-000000000004}"
13+
EndProject
1214
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ferrum.Framework", "src\Framework\Ferrum.Framework.csproj", "{B1000001-0000-0000-0000-000000000001}"
1315
EndProject
1416
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ferrum.Framework.Tests", "src\Framework.Tests\Ferrum.Framework.Tests.csproj", "{B1000002-0000-0000-0000-000000000002}"
1517
EndProject
1618
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ferrum.Codegen", "tools\codegen\Ferrum.Codegen.csproj", "{C1000001-0000-0000-0000-000000000001}"
1719
EndProject
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ferrum.Templates", "templates\Ferrum.Templates.csproj", "{C1000002-0000-0000-0000-000000000002}"
21+
EndProject
1822
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinimalDemo", "samples\MinimalDemo\MinimalDemo.csproj", "{D1000001-0000-0000-0000-000000000001}"
1923
EndProject
2024
Global
@@ -35,6 +39,10 @@ Global
3539
{C1000001-0000-0000-0000-000000000001}.Debug|Any CPU.Build.0 = Debug|Any CPU
3640
{C1000001-0000-0000-0000-000000000001}.Release|Any CPU.ActiveCfg = Release|Any CPU
3741
{C1000001-0000-0000-0000-000000000001}.Release|Any CPU.Build.0 = Release|Any CPU
42+
{C1000002-0000-0000-0000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43+
{C1000002-0000-0000-0000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU
44+
{C1000002-0000-0000-0000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{C1000002-0000-0000-0000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU
3846
{D1000001-0000-0000-0000-000000000001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3947
{D1000001-0000-0000-0000-000000000001}.Debug|Any CPU.Build.0 = Debug|Any CPU
4048
{D1000001-0000-0000-0000-000000000001}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -44,6 +52,7 @@ Global
4452
{B1000001-0000-0000-0000-000000000001} = {A0000001-0000-0000-0000-000000000001}
4553
{B1000002-0000-0000-0000-000000000002} = {A0000001-0000-0000-0000-000000000001}
4654
{C1000001-0000-0000-0000-000000000001} = {A0000002-0000-0000-0000-000000000002}
55+
{C1000002-0000-0000-0000-000000000002} = {A0000004-0000-0000-0000-000000000004}
4756
{D1000001-0000-0000-0000-000000000001} = {A0000003-0000-0000-0000-000000000003}
4857
EndGlobalSection
4958
GlobalSection(SolutionProperties) = preSolution

docs/architecture.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,11 @@ the consuming application provides the native library.
5151

5252
| File / directory | Purpose |
5353
|---|---|
54-
| `native/CMakeLists.txt` | Top-level project; includes helpers, optionally builds the test stub |
54+
| `native/CMakeLists.txt` | Top-level project; includes helpers, optionally builds the test fixtures |
5555
| `native/cmake/ferrum_helpers.cmake` | `ferrum_add_static_library()` macro that sets common flags |
5656
| `native/cmake/ios.toolchain.cmake` | Minimal iOS cross-compilation toolchain (no external deps) |
5757
| `native/test_stub/` | Trivial `ferrum_add(int, int)` C library used to validate the pipeline |
58+
| `native/dsp_fixture/` | `ferrum_dsp_scale(float*, int32_t, float)` + `ferrum_dsp_stats(const float*, int32_t, FerrumDspStats*)` — validates the float-buffer and struct-out-param patterns |
5859
| `native/scripts/build_ios.sh` | Builds all three iOS slices and packages them as an XCFramework |
5960
| `native/scripts/build_android.sh` | Builds `.so` for all four Android ABIs using the NDK toolchain |
6061

@@ -135,9 +136,14 @@ definitions.
135136
(source-generated at build time) instead of `[DllImport]` (which uses
136137
reflection at runtime). No `MarshalAs` attributes are emitted.
137138

138-
3. **Simple, dependency-free parser.** The parser is a regex/tokenizer
139-
approach — not a full C frontend. It handles the subset of C headers
140-
needed for P/Invoke: function prototypes and POD structs.
139+
3. **Simple, dependency-free parser.** The parser is a strict tokenizer —
140+
not a full C frontend. It handles the subset of C headers needed for
141+
P/Invoke: function prototypes and POD structs. Anything it does not
142+
recognise is rejected immediately (see constraint 1). A full libclang
143+
integration was considered and rejected: it would add ~40–50 MB of native
144+
binary dependencies to the dotnet tool package without materially improving
145+
correctness within the blittable-only constraint. See
146+
[open-questions.md](open-questions.md) item 5 for the full rationale.
141147

142148
### Supported C types
143149

@@ -191,5 +197,8 @@ Indicators of scope creep (flag these in code review):
191197
- A function or type name references a specific application domain
192198
(audio, video, ML model name, etc.)
193199
- The codegen tool learns about specific library ABIs by name
194-
- Any native code beyond the trivial `ferrum_add()` test stub is added to
195-
`native/test_stub/`
200+
- Any native code beyond the trivial test fixtures is added to `native/`
201+
202+
The in-tree test fixtures (`test_stub/`, `dsp_fixture/`) are explicitly exempt
203+
from the scope rule — they exist solely to prove the framework plumbing works
204+
with different parameter patterns, not to implement any application logic.

docs/getting-started.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,63 @@ dotnet build samples/MinimalDemo/MinimalDemo.csproj -f net9.0-ios -c Release
203203

204204
---
205205

206+
## NativeAOT on a real iOS device — manual verification
207+
208+
> **TODO (issue):** Automated CI verification on a physical device is not
209+
> currently wired up. Physical device builds require a paid Apple Developer
210+
> account and provisioning profiles, which cannot be stored in a public
211+
> repository. Track this as a known gap until a self-hosted runner with a
212+
> provisioned device is available.
213+
214+
Until automated CI coverage is in place, run the following steps manually
215+
whenever `NativeBuffer<T>` or the generated bindings change:
216+
217+
### Prerequisites
218+
219+
- macOS with Xcode 15+ and a valid iOS Developer provisioning profile
220+
- An iOS device running iOS 15.0 or later
221+
- .NET SDK 9.0 with the MAUI workload (`dotnet workload install maui`)
222+
223+
### Steps
224+
225+
1. **Build the native fixture:**
226+
227+
```bash
228+
./native/scripts/build_ios.sh --target ferrum_test_stub
229+
./native/scripts/build_ios.sh --target ferrum_dsp_fixture
230+
```
231+
232+
2. **Reference the XCFrameworks** in `samples/MinimalDemo/MinimalDemo.csproj`
233+
(uncomment the `<NativeReference>` items).
234+
235+
3. **Build in Release/NativeAOT mode for a physical device:**
236+
237+
```bash
238+
dotnet build samples/MinimalDemo/MinimalDemo.csproj \
239+
-f net9.0-ios \
240+
-c Release \
241+
-p:RuntimeIdentifier=ios-arm64 \
242+
-p:_BundlerExecutable=mtouch
243+
```
244+
245+
4. **Install and run on the device.** Open Xcode → Window → Devices and
246+
Simulators, drag the `.ipa` to your device, and tap it.
247+
248+
5. **Verify the following under NativeAOT** (check Xcode console output):
249+
- `NativeBuffer<float>` pins memory and returns a non-null `TypedPointer`.
250+
- `ferrum_add(3, 4)` returns `7`.
251+
- `ferrum_dsp_scale` operates in-place on a float buffer without crashing.
252+
- `ferrum_dsp_stats` fills a `FerrumDspStats` struct with correct values.
253+
- No interpreter fallback messages appear (look for `Falling back to interpreter`
254+
— there should be none).
255+
- The app does not throw `MissingRuntimeArtifactException` or any
256+
reflection-related exception.
257+
258+
6. **Record the result** in the PR description, including the device model,
259+
iOS version, and .NET SDK version used.
260+
261+
---
262+
206263
## CMake integration in your own project
207264

208265
If your native library has its own `CMakeLists.txt`, you can use the Ferrum

0 commit comments

Comments
 (0)