From e96a8558b81ae94e546f31ad860f1810eebbd19e Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Thu, 9 Apr 2026 13:38:03 -0400 Subject: [PATCH 1/4] Improve macios-binding-creator skill: version determination and enum member availability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Training session evidence: sessions d8792953 (SystemConfiguration), 91f91750 (MediaSetup), 28911011 (Photos). User corrections showed two skill gaps: 1. Availability version determination: The skill previously directed agents to use SdkVersions.cs for ALL availability versions, which is only correct for brand-new APIs. When introducing a framework on a new platform (e.g., MediaSetup → MacCatalyst), the actual introduction version differs from the current SDK version. Now directs agents to use generated reference bindings as the primary source of truth for introduction versions. 2. Per-member enum availability: When adding new members to existing enums, each member needs its own availability attribute with the member's introduction version. Added explicit guidance and code example. Multi-model validation (Sonnet 4, GPT-5.1, Haiku 4.5): Before: Accuracy 2/5, Completeness 2/5, Clarity 2/5 After: Accuracy 5/5, Completeness 5/5, Clarity 5/5 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/macios-binding-creator/SKILL.md | 21 ++++++--- .../references/binding-patterns.md | 45 +++++++++++++++---- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/.agents/skills/macios-binding-creator/SKILL.md b/.agents/skills/macios-binding-creator/SKILL.md index f6aa617120ea..2120bf5cf3ff 100644 --- a/.agents/skills/macios-binding-creator/SKILL.md +++ b/.agents/skills/macios-binding-creator/SKILL.md @@ -70,16 +70,26 @@ Before implementing, understand the native API: #### Determine the Correct Availability Version -Before writing any bindings, determine the SDK version you're targeting: +Before writing any bindings, determine the correct availability version for each API. The version represents **when Apple introduced the API**, not the current SDK version. + +**Primary source of truth: the generated reference bindings** from Step 2. After running `make -C tests/xtro-sharpie gen-all`, search the generated `.cs` files for the API you're binding — they include `[Introduced]` attributes extracted from Apple's SDK headers with the correct per-platform introduction versions. Always use these versions. + +```bash +# Find the generated reference binding for a specific API +grep -r "SomeClassName\|SomeMethodName" tests/xtro-sharpie/api-annotations-dotnet/generated/ +``` + +If the generated reference bindings don't include version information, fall back to these sources: +1. **Apple SDK headers** — search under `$XCODE_DEVELOPER_ROOT` for `API_AVAILABLE` macros +2. **Current SDK version from `SdkVersions.cs`** — use this only for **brand-new APIs** introduced in the current Xcode release: ```bash -# Check the current SDK versions grep -E 'public const string (iOS|TVOS|OSX|MacCatalyst) ' tools/common/SdkVersions.cs -# Or from Make.versions -grep '_NUGET_OS_VERSION=' Make.versions ``` -Use the version from `SdkVersions.cs` (e.g., `26.2`) for all availability attributes. If the user specifies a different version (e.g., binding a beta branch at `26.4`), use that instead. **Ask the user if you're unsure which version to use.** +> ❌ **NEVER assume the current SDK version is the introduction version for all APIs.** The SDK version (e.g., `26.5`) is only correct for APIs that are genuinely new in this Xcode release. When adding an existing framework to a new platform (e.g., MediaSetup to MacCatalyst), or adding enum members that were introduced in an earlier release, the introduction version will be different — check the generated reference bindings or Apple headers. + +If the user specifies a version, use that instead. **Ask the user if you're unsure which version to use.** #### File Locations @@ -109,6 +119,7 @@ NSString ScheduleRequestedNotification { get; } > - API definition interfaces and members in `src/frameworkname.cs` — use `[iOS (X, Y)]`, `[Mac (X, Y)]`, etc. > - P/Invoke wrappers and manual properties in `src/FrameworkName/*.cs` — use `[SupportedOSPlatform ("iosX.Y")]`, `[SupportedOSPlatform ("macos")]`, etc. > - Fields, constants, and enum values +> - **Individual enum members** added to an existing enum — each new member needs its own `[iOS (X, Y)]` etc. with the version the **member** was introduced, even if the enum itself has an older version. Check the generated reference bindings for the correct per-member version. > ❌ **NEVER** use `string.Empty` — use `""`. Never use `Array.Empty()` — use `[]`. diff --git a/.agents/skills/macios-binding-creator/references/binding-patterns.md b/.agents/skills/macios-binding-creator/references/binding-patterns.md index 895b9661d65e..75f99f495ba5 100644 --- a/.agents/skills/macios-binding-creator/references/binding-patterns.md +++ b/.agents/skills/macios-binding-creator/references/binding-patterns.md @@ -158,6 +158,23 @@ public enum SomeEnum : long { NSString SelectedOption { get; set; } ``` +### Adding New Members to Existing Enums + +When adding a new value to an existing enum, the new member needs its own availability attribute with the version that **member** was introduced — not the enum's introduction version: + +```csharp +[iOS (18, 0)] +[NoTV, NoMacCatalyst, NoMac] +public enum PHAssetResourceUploadJobAction : long { + Acknowledge = 0, + Retry = 1, + [iOS (26, 5)] + Process = 3, +} +``` + +Check the generated reference bindings (`make -C tests/xtro-sharpie gen-all`) for the correct per-member introduction version. + ## Notification Fields ```csharp @@ -723,19 +740,29 @@ Both styles are required. Omitting availability from P/Invokes or manual propert ### Determining the Correct Version -Check `tools/common/SdkVersions.cs` for the current SDK versions: +The availability version represents **when Apple introduced the API**, not the current SDK version. Use these sources in order: -```bash -grep -E 'public const string (iOS|TVOS|OSX|MacCatalyst) ' tools/common/SdkVersions.cs -``` +1. **Generated reference bindings** (best source) — after running `make -C tests/xtro-sharpie gen-all`, search for the API in the generated `.cs` files. These include `[Introduced]` attributes extracted from Apple's SDK headers: + ```bash + grep -r "SomeApiName" tests/xtro-sharpie/api-annotations-dotnet/generated/ + ``` -Or check `Make.versions`: +2. **Apple SDK headers** — search for `API_AVAILABLE` macros under `$XCODE_DEVELOPER_ROOT` -```bash -grep '_NUGET_OS_VERSION=' Make.versions -``` +3. **Current SDK version** (`SdkVersions.cs`) — use only for **brand-new APIs** introduced in the current Xcode release: + ```bash + grep -E 'public const string (iOS|TVOS|OSX|MacCatalyst) ' tools/common/SdkVersions.cs + ``` + +If the user specifies a different version (e.g., for a beta branch), use that instead. + +### Common Version Mistakes -Use these values for all availability attributes. If the user specifies a different version (e.g., for a beta branch), use that instead. +| Scenario | ❌ Wrong | ✅ Correct | +|----------|---------|-----------| +| Framework introduced on a new platform (e.g., MediaSetup → MacCatalyst) | Use current SDK version (26.5) | Research when Apple actually introduced it on that platform (could be 16.0) | +| New enum member added to existing enum | No per-member attribute, or enum-level version | Per-member attribute with the member's own introduction version | +| Brand-new API in current Xcode | — | Current SDK version from `SdkVersions.cs` is correct | ## Monotouch-Test Patterns From 07b23365a77e60288656afbe0f3529a87bb3761e Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Thu, 9 Apr 2026 13:57:35 -0400 Subject: [PATCH 2/4] skills: Add NativeObject marshal type registration guidance to binding-creator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add documentation for registering NativeObject subclasses as bgen marshal types, addressing a critical knowledge gap discovered during PrintCore binding work (session 1a7e421e). Without this guidance, agents default to IntPtr returns for protocol methods even when concrete managed wrappers exist, requiring extensive user intervention to discover the TypeCache + MarshalTypeList registration pattern. Changes: - New section 'NativeObject Return Types in Protocol Methods' in binding-patterns.md with full 5-step workflow: CORE_SOURCES → #if !COREBUILD guards → TypeCache registration → MarshalTypeList registration → use concrete types in API definitions - Enhanced frameworks.sources docs to explain FRAMEWORKNAME_CORE_SOURCES for making types visible to bgen's core assembly - Extended #if !COREBUILD guidance from struct-only to include NativeObject class shells (referencing StoreProductParameters.cs, CGColor.cs patterns) - Added cross-reference in SKILL.md Step 4 warning against IntPtr when managed NativeObject wrappers exist Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/macios-binding-creator/SKILL.md | 2 + .../references/binding-patterns.md | 104 ++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/.agents/skills/macios-binding-creator/SKILL.md b/.agents/skills/macios-binding-creator/SKILL.md index 2120bf5cf3ff..13cbcbeaf948 100644 --- a/.agents/skills/macios-binding-creator/SKILL.md +++ b/.agents/skills/macios-binding-creator/SKILL.md @@ -135,6 +135,8 @@ NSString ScheduleRequestedNotification { get; } > ❌ **NEVER** use `#pragma warning disable 0169` for struct fields. Instead, wrap public methods and properties inside `#if !COREBUILD` (but NOT fields — bgen needs to know the struct size). +> ⚠️ **Protocol methods returning opaque types**: If a protocol method returns an opaque C type (e.g., `PMPrintSession`) that has a managed `NativeObject` wrapper in `src/FrameworkName/`, do NOT use `IntPtr`. Register the type as a bgen marshal type so bgen can generate proper `Runtime.GetINativeObject()` marshaling. See [references/binding-patterns.md](references/binding-patterns.md) § "NativeObject Return Types in Protocol Methods". + > ⚠️ Place a space before parentheses and brackets: `Foo ()`, `Bar (1, 2)`, `myarray [0]`. > ⚠️ Method names should follow .NET naming conventions — use verb-based names, not direct Objective-C selector translations (e.g., `BuildMenu` not `MenuWithContents`). diff --git a/.agents/skills/macios-binding-creator/references/binding-patterns.md b/.agents/skills/macios-binding-creator/references/binding-patterns.md index 75f99f495ba5..53664c6d3fc7 100644 --- a/.agents/skills/macios-binding-creator/references/binding-patterns.md +++ b/.agents/skills/macios-binding-creator/references/binding-patterns.md @@ -526,6 +526,109 @@ Real example: `src/MapKit/MKMultiPoint.cs` Add the manual file to the framework's `*_SOURCES`. If the file defines types needed by the API definition (like structs), add it to both `*_API_SOURCES` and `*_SOURCES`. +If the file defines types that bgen needs to resolve (e.g., NativeObject subclasses used as marshal types — see "NativeObject Return Types in Protocol Methods" below), add it to `*_CORE_SOURCES`: + +``` +# In src/frameworks.sources: +PRINTCORE_CORE_SOURCES = \ + PrintCore/Defs.cs \ + PrintCore/PrintCore.cs \ +``` + +`*_CORE_SOURCES` files are compiled into bgen's core assembly, making their types available for type resolution during code generation. Use this when bgen reports `BI1078: Do not know how to make a signature for `. + +## NativeObject Return Types in Protocol Methods + +When a protocol method returns an opaque C type (e.g., `PMPrintSession`, `PMPageFormat`) that has a managed `NativeObject` wrapper, bgen doesn't know how to marshal it by default. Bgen only handles `NSObject` subclasses, primitives, and `IntPtr` in protocol return positions. + +Types like `CGColor`, `CGImage`, and `CMSampleBuffer` work because they're explicitly registered as **marshal types** in bgen. The default `MarshalType` generates `Runtime.GetINativeObject(ptr, owns)` marshaling — exactly what NativeObject wrappers need. + +### Recognition + +You need this pattern when: +- A protocol method returns an opaque C type that has a managed `NativeObject` wrapper +- bgen reports `BI1078: Do not know how to make a signature for ` +- You've been using `IntPtr` as the return type but a concrete managed type exists + +### Step 1: Add to CORE_SOURCES + +The type's manual code file must be in `FRAMEWORKNAME_CORE_SOURCES` in `src/frameworks.sources` so bgen's core assembly can resolve it. See the `frameworks.sources` section above. + +### Step 2: Add #if !COREBUILD guards + +In the manual code file (e.g., `src/PrintCore/PrintCore.cs`), wrap the class **body** in `#if !COREBUILD` but keep the class shell visible. This is the same pattern used in `src/StoreKit/StoreProductParameters.cs`: + +```csharp +#nullable enable + +using System; +using System.Runtime.InteropServices; +using ObjCRuntime; + +namespace PrintCore { + + public class PMPrintSession : PMPrintCoreBase { +#if !COREBUILD + [Preserve (Conditional = true)] + internal PMPrintSession (NativeHandle handle, bool owns) + : base (handle, owns) + { + } + + // ... constructors, properties, methods ... +#endif // !COREBUILD + } +} +``` + +The class shell (name, inheritance) is visible to bgen's core assembly for type resolution. The body (constructors, methods) is excluded from core build since bgen only needs the type identity, not the implementation. + +> ⚠️ **Use `#if !COREBUILD`, not separate files.** Don't split the type into a shell file and an implementation file. The `#if !COREBUILD` pattern keeps everything in one file and is the established convention (see `src/StoreKit/StoreProductParameters.cs`, `src/CoreGraphics/CGColor.cs`). + +### Step 3: Register in TypeCache + +Add a property and lookup entry in `src/bgen/Caches/TypeCache.cs`: + +```csharp +// Add property +public TypeReference PMPrintSession { get; } + +// Add in Lookup method +case "PMPrintSession": + PMPrintSession = type; + break; +``` + +### Step 4: Register in MarshalTypeList + +Add the type to `src/bgen/Models/MarshalTypeList.cs`: + +```csharp +Add (types.PMPrintSession); +``` + +The default `MarshalType` constructor generates `Runtime.GetINativeObject(ptr, owns)` marshaling, which is correct for all `NativeObject` subclasses. + +### Step 5: Use concrete types in API definition + +Now replace `IntPtr` return types with the concrete type in the bgen file: + +```csharp +// Before — IntPtr with XML docs suggesting cast +[Export ("printSession")] +IntPtr PrintSession { get; } + +// After — concrete type, bgen handles marshaling +[Export ("printSession")] +PMPrintSession PrintSession { get; } +``` + +### Real Example + +In the PrintCore framework, `PMPrintSession`, `PMPrintSettings`, `PMPageFormat`, `PMPrinter`, and `PMPaper` were all registered as marshal types so protocol methods in `PDEPanel`, `PDEPlugIn`, and `PDEPlugInCallbackProtocol` could return them directly instead of `IntPtr`. + +> ❌ **NEVER leave protocol methods returning IntPtr when a managed NativeObject wrapper exists.** Always check the `src/FrameworkName/` directory for existing wrapper classes before using `IntPtr`. If a wrapper exists, register it as a bgen marshal type using this pattern. + ## Strongly-Typed Dictionaries ```csharp @@ -658,6 +761,7 @@ All `[Verify]` attributes must be resolved before submitting a PR. - **XAMCORE_5_0**: Only for fixing breaking changes on existing shipped types. Never use for new code. See "XAMCORE_5_0 Pattern for Existing Types" below. - **Handle access in manual code**: Use `GetCheckedHandle ()` instead of `Handle` when passing the native handle to P/Invokes in manual bindings. `GetCheckedHandle ()` throws `ObjectDisposedException` if the object has been disposed, preventing hard-to-debug native crashes. - **Struct members**: Wrap public methods and properties in `#if !COREBUILD`, but NOT fields (bgen needs struct size). Never use `#pragma warning disable 0169`. +- **NativeObject class shells**: When a NativeObject type is in `CORE_SOURCES`, wrap the class body in `#if !COREBUILD` but keep the class declaration visible. See "NativeObject Return Types in Protocol Methods" above. - **String types**: Use `string` (not `NSString`) for string parameters in methods, properties, and delegates. The binding generator handles marshaling automatically. Only use `NSString` for dictionary keys or strong-typed constants. ## XAMCORE_5_0 Pattern for Existing Types From 6e8e11150363f7395129b9f3834bbb895a71d36e Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Thu, 9 Apr 2026 19:01:24 -0400 Subject: [PATCH 3/4] skills: Add naming, platform directive, and callback handler guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Train the macios-binding-creator skill based on ARKit binding session (62c564f6) where the agent needed user corrections for three issues: 1. Named types Ar* instead of AR* — the naming guidance was ambiguous about ObjC class prefixes vs .NET acronym rules. Added explicit rule that C# type names preserve ObjC prefix casing exactly (AR*, AV*, CG*) while .NET acronym rules only apply to property/method names. 2. Used MACOS_DOTNET_SOURCES instead of #if MONOMAC — added guidance and anti-pattern clarifying that preprocessor directives are preferred over platform-specific source file lists, with a table of available symbols. 3. Implemented callback handler with null safety issues — added new 'C Callback Handler Binding' section covering BlockLiteral trampoline and GCHandle patterns, with rules for null safety on nullable parameters and memory management ordering when replacing handlers. Also adds the training log documenting both training sessions (PrintCore and ARKit) with assessment, changes, and patterns learned. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../training-log-macios-binding-creator.md | 131 ++++++++++++++++++ .../skills/macios-binding-creator/SKILL.md | 10 ++ .../references/binding-patterns.md | 105 +++++++++++++- 3 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 .agents/files/training-log-macios-binding-creator.md diff --git a/.agents/files/training-log-macios-binding-creator.md b/.agents/files/training-log-macios-binding-creator.md new file mode 100644 index 000000000000..b425a67eada2 --- /dev/null +++ b/.agents/files/training-log-macios-binding-creator.md @@ -0,0 +1,131 @@ +# Training Log: macios-binding-creator + +## Session: 2026-04-09 (3) — Version determination and enum member availability + +**Trainer:** SkillTrainer | **Skill:** macios-binding-creator | **Trigger:** User request to enhance skill using copilot session d8792953-287f-485e-aed6-d4a6d46043c8 (SystemConfiguration bindings) + +### Assessment + +**Source:** Session d8792953 — "Update SystemConfiguration Bindings" (1 turn, 2026-04-09). Cross-referenced with 16 other binding-creator sessions, particularly: +- Session 91f91750 (MediaSetup, 3 turns) — User corrected: agent used `[MacCatalyst (26, 5)]` when framework was actually introduced at `(16, 0)` +- Session 28911011 (Photos, 2 turns) — User corrected: agent forgot `[iOS (26, 5)]` on new enum member `Process = 3` + +Multi-model pre-assessment (Sonnet 4, GPT-5.1, Haiku 4.5) scored skill at **Accuracy 2/5, Completeness 2/5, Clarity 2/5** on two behavioral scenarios. All 3 models reproduced both failure modes when following the skill literally. + +**Issues found (ranked):** +1. ❌ **Wrong: SDK version conflated with introduction version** — Skill said "use SdkVersions.cs version for all availability attributes" which is only correct for brand-new APIs. When introducing a framework on a new platform, the actual historical introduction version is needed. All 3 models used `[MacCatalyst (26, 5)]` instead of `[MacCatalyst (16, 0)]`. +2. ❌ **Missing: Per-member enum availability** — Skill mentioned "every new API" needs attributes but didn't specifically call out individual enum members added to existing enums. 1 of 3 models omitted the per-member attribute entirely; the other 2 added it but noted the skill was ambiguous. +3. ⚠️ **Missing: Version research methodology** — No guidance on how to determine when an API was actually introduced. Generated reference bindings (from `make -C tests/xtro-sharpie gen-all`) contain `[Introduced]` attributes from Apple headers — this was not mentioned as a source. + +### Cycle 1: Fix version determination + enum member availability + +**Hypothesis:** Rewriting the version determination section to use generated reference bindings as primary source of truth (instead of SdkVersions.cs) will prevent wrong availability versions. Adding explicit per-member enum guidance will prevent omitted attributes. + +**Edits:** +1. `SKILL.md` — Rewrote "Determine the Correct Availability Version" subsection in Step 4. Changed primary source from SdkVersions.cs to generated reference bindings. Added fallback hierarchy (Apple headers → SdkVersions.cs for brand-new APIs only). Added ❌ NEVER rule against assuming SDK version = introduction version, with MediaSetup as example. +2. `SKILL.md` — Enhanced the availability bullet list to explicitly include "Individual enum members added to an existing enum" with guidance to check generated reference bindings for per-member versions. +3. `references/binding-patterns.md` — New "Adding New Members to Existing Enums" subsection under Enum Bindings with code example showing `[iOS (26, 5)]` on a new member within an `[iOS (18, 0)]` enum. +4. `references/binding-patterns.md` — Rewrote "Determining the Correct Version" subsection with prioritized source list (generated reference bindings → Apple headers → SdkVersions.cs) and new "Common Version Mistakes" table covering framework-on-new-platform, new-enum-member, and brand-new-API scenarios. + +**Validation (same 3 models, same 2 scenarios):** + +| Metric | Before | After | +|--------|--------|-------| +| Accuracy (avg) | 2/5 | **5/5** | +| Completeness (avg) | 2/5 | **5/5** | +| Clarity (avg) | 2/5 | **5/5** | + +All 3 models now correctly: use `[MacCatalyst (16, 0)]` from generated reference bindings (not `26, 5`), add per-member `[iOS (26, 5)]` on new enum values, and cite the generated reference bindings as the source of truth. + +**Outcome:** ✅ Committed as `e96a8558b8`. No regressions detected. + +### Patterns Learned +- **Generated reference bindings are the best version source** — `make -C tests/xtro-sharpie gen-all` produces `.cs` files with `[Introduced]` attributes extracted from Apple SDK headers. These contain the correct per-platform, per-member introduction versions. The skill previously didn't mention this as a version source at all. +- **SDK version ≠ introduction version** — SdkVersions.cs gives the current Xcode SDK version (e.g., 26.5), which is correct for brand-new APIs but wrong for APIs that were introduced in earlier releases or on other platforms. The distinction must be explicit in the skill. +- **Enum members need individual availability** — When a new value is added to an existing enum, it needs its own `[iOS (X, Y)]` attribute with the member's introduction version, separate from the enum-level attribute. This was a common oversight because the skill only said "every new API" without calling out this specific case. + +### Open Items +- None — both ranked issues addressed. Stop signal met (≥4/5 across 3 models, 2 families). + +--- + +## Session: 2026-04-09 (2) — Naming prefixes, platform directives, callback patterns + +**Trainer:** SkillTrainer | **Skill:** macios-binding-creator | **Trigger:** User request to enhance skill using copilot session 62c564f6-99e3-47f5-b523-d206c665b71d (ARKit bindings) + +### Assessment + +**Source:** Session 62c564f6 — "Update ARKit C# Bindings" (9 turns, 2026-04-09) + +The session revealed three gaps: the agent named new ARKit types `Ar*` instead of `AR*` (turn 8 correction), used `MACOS_DOTNET_SOURCES` instead of `#if MONOMAC` (turn 7 correction), and implemented a callback handler with null safety issues on nullable `DispatchQueue?` parameters (turn 1 code review). + +**Issues found (ranked):** +1. ❌ **Wrong: ObjC type prefix casing** — Agent applied .NET acronym rules to type name prefixes, producing `ArSession` instead of `ARSession`. Current guidance ("Acronyms shouldn't be all uppercase") was ambiguous — it applies to property/method names, not type prefixes. +2. ⚠️ **Incomplete: Platform-specific code pattern** — Agent used `MACOS_DOTNET_SOURCES` in frameworks.sources for macOS-only code. User corrected to `#if MONOMAC`. Skill only documented `#if !TVOS`, not the full set of platform directives. +3. ⚠️ **Missing: C callback handler binding pattern** — Agent implemented GCHandle-based callback handler with null safety issues on nullable parameters and incorrect memory management ordering. No guidance existed for this pattern. + +### Cycle 1: Fix naming + platform directives + callback pattern + +**Hypothesis:** Explicitly distinguishing type name prefixes from .NET acronym rules will prevent the `Ar*` vs `AR*` mistake. Documenting preprocessor symbols and anti-pattern for source file lists will prevent `MACOS_DOTNET_SOURCES` misuse. Adding callback handler patterns will prevent null safety and memory management bugs. + +**Edits:** +1. `references/binding-patterns.md` — Rewrote naming bullet in Common Pitfalls to explicitly state type names preserve ObjC prefix exactly, with examples (ARSession, AVPlayer, CGColor). +2. `references/binding-patterns.md` — New "Platform-Specific Code Within Shared Files" subsection with preprocessor symbol table and anti-pattern. +3. `references/binding-patterns.md` — New "C Callback Handler Binding" subsection with BlockLiteral trampoline and GCHandle patterns, null safety rules, memory management ordering. +4. `SKILL.md` — Added ❌ NEVER rule about ObjC class prefix casing in Step 4. +5. `SKILL.md` — Enhanced Step 4b with platform directive anti-pattern and preprocessor symbol list. + +**Outcome:** ✅ Changes kept. All five edits address direct user corrections or code review findings from the session. + +### Patterns Learned +- **Type name prefix ≠ acronym** — The .NET "don't use all-caps acronyms" rule only applies within property/method names. ObjC type prefixes (AR*, AV*, CG*, CK*) are preserved exactly. The existing skill guidance was ambiguous enough for the agent to misapply it. +- **Preprocessor directives over source file lists** — The codebase convention is `#if MONOMAC` in shared files, not per-platform source file entries in frameworks.sources. +- **Callback handler null safety** — When a setter method has both a nullable handler and a nullable queue, ALL code paths must null-check both parameters independently. The "handler is null → pass null to native" shortcut still needs to handle the queue parameter safely. + +### Open Items +- None — all three ranked issues addressed. + +--- + +## Session: 2026-04-09 (1) — NativeObject marshal type guidance + +**Trainer:** SkillTrainer | **Skill:** macios-binding-creator | **Trigger:** User request to enhance skill using copilot session 1a7e421e-e4ca-44bc-a5d1-4d745db2ed06 (PrintCore bindings) + +### Assessment + +**Source:** Session 1a7e421e — "Update PrintCore Bindings" (12 turns, 2026-04-08 to 2026-04-09) + +The session revealed a critical knowledge gap: when binding protocol methods that return opaque C types with managed NativeObject wrappers (PMPrintSession, PMPrintSettings, PMPageFormat, PMPrinter), the agent initially used IntPtr returns and required 8 turns of user guidance to discover the bgen marshal type registration pattern. The user had to: +1. Ask the agent to research concrete types (turn 1) +2. Request Runtime.GetINativeObject docs (turns 2-3) +3. Guide through CORE_SOURCES + IntPtr → concrete type (turn 6) +4. Correct the file-splitting approach to #if !COREBUILD (turn 7) +5. Point to CGColor as a precedent for marshal types (turn 8) +6. Approve the TypeCache + MarshalTypeList fix (turn 9) + +**Issues found (ranked):** +1. ❌ **Missing: NativeObject marshal type registration** — No guidance on the multi-step process for making NativeObject types work as bgen return types (TypeCache.cs + MarshalTypeList.cs + CORE_SOURCES). Agent defaulted to IntPtr and couldn't self-correct. +2. ⚠️ **Incomplete: CORE_SOURCES in frameworks.sources** — Skill only mentioned `*_SOURCES` and `*_API_SOURCES`, not `*_CORE_SOURCES` for making types visible to bgen's core assembly. +3. ⚠️ **Incomplete: #if !COREBUILD for NativeObject shells** — Guidance only covered struct properties. Agent tried splitting into separate files instead of using the established #if !COREBUILD pattern for class shells. + +### Cycle 1: Add NativeObject marshal type registration section + +**Hypothesis:** Adding a comprehensive "NativeObject Return Types in Protocol Methods" section to binding-patterns.md will prevent agents from using IntPtr when concrete types exist, and will guide them through the TypeCache + MarshalTypeList registration process. + +**Evidence:** Session 1a7e421e showed the agent needed 8 turns of guidance. The pattern is well-established (CGColor, CGImage, CMSampleBuffer are all registered this way) but undocumented in the skill. + +**Edits:** +1. `references/binding-patterns.md` — New section "NativeObject Return Types in Protocol Methods" with recognition criteria, step-by-step process (CORE_SOURCES → #if !COREBUILD → TypeCache → MarshalTypeList → use concrete type), code examples, real example (PrintCore), and anti-pattern. +2. `references/binding-patterns.md` — Enhanced `frameworks.sources` subsection to explain `*_CORE_SOURCES` with code example and when to use it (BI1078 error). +3. `references/binding-patterns.md` — Added NativeObject class shell guidance to Common Pitfalls `#if !COREBUILD` bullet. +4. `SKILL.md` — Added cross-reference warning in Step 4 about protocol methods returning opaque types, pointing to binding-patterns.md. + +**Outcome:** ✅ Changes kept. All four edits are minimal and targeted — no bloat, each adds information the agent couldn't discover on its own. + +### Patterns Learned +- **bgen marshal type registration is a 4-file change** — TypeCache.cs (property + lookup), MarshalTypeList.cs (Add call), frameworks.sources (CORE_SOURCES), and the manual code file (#if !COREBUILD guards). All four steps must be documented together since missing any one causes different errors. +- **#if !COREBUILD is the universal pattern** — Not just for structs. NativeObject class shells use it identically. The skill should present it as a general pattern, not struct-specific. +- **Precedent-based learning works** — The user pointing to CGColor as a precedent was the key breakthrough. The skill should name precedent types to help agents self-discover. + +### Open Items +- None — all three ranked issues addressed in this cycle. diff --git a/.agents/skills/macios-binding-creator/SKILL.md b/.agents/skills/macios-binding-creator/SKILL.md index 13cbcbeaf948..4995cef76478 100644 --- a/.agents/skills/macios-binding-creator/SKILL.md +++ b/.agents/skills/macios-binding-creator/SKILL.md @@ -141,6 +141,8 @@ NSString ScheduleRequestedNotification { get; } > ⚠️ Method names should follow .NET naming conventions — use verb-based names, not direct Objective-C selector translations (e.g., `BuildMenu` not `MenuWithContents`). +> ❌ **NEVER** change the casing of Objective-C class name prefixes in C# type names. `ARSession` stays `ARSession` (not `ArSession`), `AVPlayer` stays `AVPlayer` (not `AvPlayer`). When creating new manual types, match the framework's established prefix (e.g., all ARKit types use `AR*`, all CoreGraphics types use `CG*`). The .NET acronym rules (SIMD → Simd, URL → Url) only apply within property and method names, NOT to type name prefixes. + > ⚠️ For in depth binding patterns and conventions See [references/binding-patterns.md](references/binding-patterns.md) > ⚠️ **Struct array parameters**: When an API takes a C struct pointer + count (e.g., `MyStruct*` + `NSUInteger`), bind the raw pointer as `[Internal]` with `IntPtr`, then create a manual public wrapper using the **factory pattern** with `fixed`. See [references/binding-patterns.md](references/binding-patterns.md) § "Struct Array Parameter Binding". @@ -161,6 +163,14 @@ using MyStruct = Foundation.NSObject; The `[NoTV]` attribute on the API definition interface ensures the type won't appear in the final tvOS assembly, while the alias prevents compilation errors from method signatures that reference the struct. +> ❌ **NEVER** use platform-specific source file lists (e.g., `FRAMEWORKNAME_MACOS_DOTNET_SOURCES`) for platform-conditional code. Instead, use preprocessor directives (`#if MONOMAC`, `#if !TVOS`, `#if __IOS__`) within shared source files. Platform-specific source file lists are for the build system, not for conditional compilation of individual types or members. + +Available preprocessor symbols for platform checks: +- `MONOMAC` / `__MACOS__` — macOS +- `__IOS__` — iOS +- `TVOS` / `__TVOS__` — tvOS +- `__MACCATALYST__` — Mac Catalyst + ### Step 5: Build ```bash diff --git a/.agents/skills/macios-binding-creator/references/binding-patterns.md b/.agents/skills/macios-binding-creator/references/binding-patterns.md index 53664c6d3fc7..95e4bd678abe 100644 --- a/.agents/skills/macios-binding-creator/references/binding-patterns.md +++ b/.agents/skills/macios-binding-creator/references/binding-patterns.md @@ -349,6 +349,80 @@ public struct MyStruct { public static NSString MyConstant { get; } ``` +### C Callback Handler Binding + +When a C API sets a persistent callback handler (e.g., state change handlers, data providers), use the **BlockLiteral trampoline** pattern for Objective-C blocks or **GCHandle** for C function pointer contexts. Follow these patterns from `src/Network/` and `src/CoreFoundation/`. + +#### BlockLiteral Trampoline (preferred for ObjC block callbacks) + +This is the standard pattern used in Network framework (`NWConnection`, `NWBrowser`, `NWListener`): + +```csharp +[DllImport (Constants.NetworkLibrary)] +static extern void nw_connection_set_state_changed_handler ( + IntPtr handle, /* BlockLiteral* */ IntPtr handler); + +[UnmanagedCallersOnly] +static void TrampolineStateChanged (IntPtr block, int state, IntPtr error) +{ + var del = BlockLiteral.GetTarget> (block); + if (del is not null) + del (/* marshal args */); +} + +public void SetStateChangedHandler (Action handler) +{ + if (handler is null) { + nw_connection_set_state_changed_handler (GetCheckedHandle (), IntPtr.Zero); + return; + } + + unsafe { + // Must use 'static' lambda or named method for function pointer + delegate* unmanaged trampoline = &TrampolineStateChanged; + using var block = new BlockLiteral (trampoline, handler, typeof (MyClass), + nameof (TrampolineStateChanged)); + nw_connection_set_state_changed_handler (GetCheckedHandle (), (IntPtr) (&block)); + } +} +``` + +#### GCHandle Context (for C function pointer + void* context) + +Used in CoreFoundation (`CFStream.cs`) when the native API takes a function pointer + context: + +```csharp +GCHandle gch; + +void EnableEvents () +{ + if (!gch.IsAllocated) + gch = GCHandle.Alloc (this); + + var ctx = new CFStreamClientContext { + Info = GCHandle.ToIntPtr (gch) + }; + DoSetClient (&NativeCallback, ref ctx); +} + +[UnmanagedCallersOnly] +static void NativeCallback (IntPtr stream, int eventType, IntPtr info) +{ + var instance = GCHandle.FromIntPtr (info).Target as MyClass; + instance?.OnCallback (eventType); +} +``` + +#### Key Rules for Callback Handlers + +> ❌ **NEVER** access a nullable parameter (e.g., `DispatchQueue? queue`) without null-checking it first. If `handler` is null and `queue` is also null, calling `queue.GetHandle()` throws `NullReferenceException`. Check all nullable parameters before use on every code path. + +> ⚠️ **Memory management ordering**: When replacing a stored handler, set the new managed reference **before** freeing the old `GCHandle`. This prevents premature collection if GC runs between the free and the assignment. + +> ⚠️ **GC.KeepAlive**: Call `GC.KeepAlive (queue)` or `GC.KeepAlive (handler)` after passing native handles to P/Invokes. This prevents the GC from collecting the managed object while the native call is still using its handle. + +Real examples: `src/Network/NWConnection.cs`, `src/Network/NWBrowser.cs`, `src/CoreFoundation/CFStream.cs`, `src/Security/SecTrust.cs` + ### Struct Binding Rules - **Only use blittable types as backing fields in structs.** `bool` and `char` aren't blittable — use `byte` and `ushort`/`short` instead. This avoids `[MarshalAs]` and cecil test known failures. @@ -385,6 +459,35 @@ using MyStruct = Foundation.NSObject; The type alias lets tvOS compilation succeed. The `[NoTV]` attribute on the API definition interface ensures the type won't appear in the final tvOS assembly. +### Platform-Specific Code Within Shared Files + +Use preprocessor directives for platform-conditional code — **not** platform-specific source file lists in `frameworks.sources`: + +```csharp +// macOS-only type or member +#if MONOMAC +public class ARCollaborationData : NSObject { + // macOS-only implementation +} +#endif + +// iOS-specific behavior +#if __IOS__ + [DllImport (Constants.ARKitLibrary)] + static extern void ar_session_run (IntPtr session, IntPtr config); +#endif +``` + +Available preprocessor symbols: +| Symbol | Platform | +|--------|----------| +| `MONOMAC` / `__MACOS__` | macOS | +| `__IOS__` | iOS | +| `TVOS` / `__TVOS__` | tvOS | +| `__MACCATALYST__` | Mac Catalyst | + +> ❌ **NEVER** use platform-specific source file entries (e.g., `FRAMEWORKNAME_MACOS_DOTNET_SOURCES`) for conditional compilation. Use `#if` directives instead — they keep the code in shared files and are the established convention across the codebase. + ## Struct Array Parameter Binding When an Objective-C API takes a C struct pointer + count (e.g., `MyStruct*` + `NSUInteger`), create a manual public wrapper that marshals a managed array to/from the native pointer. This is a common Apple API pattern (MapKit, CarPlay, ARKit, etc.). @@ -754,7 +857,7 @@ All `[Verify]` attributes must be resolved before submitting a PR. - **Null handling**: Always use `[NullAllowed]` where Apple's docs indicate nullability. Default assumption is non-null. However, if a `[DesignatedInitializer]` constructor crashes (segfault) when passed null, **remove `[NullAllowed]`** — the native API genuinely doesn't accept null, and removing it is better than adding introspection test exclusions. - **Struct backing fields**: Only use blittable types. `bool` and `char` aren't blittable — use `byte` and `ushort`/`short` instead, with typed property accessors. - **Threading**: UI APIs require main thread. Use `[ThreadSafe]` for thread-safe APIs. -- **Naming**: Follow .NET PascalCase for methods/properties. Remove redundant ObjC prefixes (`NSString name` → `string Name`). Acronyms shouldn't be all uppercase (SIMD → Simd, ID → Id when it means "identifier", URL → Url). Methods should be verbs, properties should be nouns. Don't blindly translate ObjC selector names — use .NET-appropriate verb names (e.g., `BuildMenu` not `MenuWithContents`). +- **Naming**: Follow .NET PascalCase for methods/properties. Remove redundant ObjC prefixes (`NSString name` → `string Name`). **C# type names preserve the Objective-C class prefix exactly** — `ARSession` stays `ARSession` (not `ArSession`), `AVPlayer` stays `AVPlayer` (not `AvPlayer`), `CGColor` stays `CGColor` (not `CgColor`). When creating new manual types for a framework, match the established prefix (e.g., new ARKit types use `AR*`). The .NET acronym casing rules only apply within property/method names (SIMD → Simd, ID → Id when it means "identifier", URL → Url). Methods should be verbs, properties should be nouns. Don't blindly translate ObjC selector names — use .NET-appropriate verb names (e.g., `BuildMenu` not `MenuWithContents`). - **Selectors**: Must match exactly — a single typo causes runtime crashes. - **Protocol conformance**: All `[Abstract]` methods in a protocol are required. - **nint/nuint**: Use `nint`/`nuint` for Objective-C `NSInteger`/`NSUInteger`. From bd8d5d8463f4cf1a159fb76f5f0f8f255d2e0db4 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Thu, 9 Apr 2026 21:48:10 -0400 Subject: [PATCH 4/4] More updates --- .../training-log-macios-binding-creator.md | 35 ++++++++++++++++ .../skills/macios-binding-creator/SKILL.md | 20 ++++++++- .../references/binding-patterns.md | 41 +++++++++++++++++++ .../references/test-workflow.md | 26 +++++++++--- 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/.agents/files/training-log-macios-binding-creator.md b/.agents/files/training-log-macios-binding-creator.md index b425a67eada2..85fd6835ba64 100644 --- a/.agents/files/training-log-macios-binding-creator.md +++ b/.agents/files/training-log-macios-binding-creator.md @@ -1,5 +1,40 @@ # Training Log: macios-binding-creator +## Session: 2026-04-10 (1) — Mixed API surface frameworks, .todo cleanup, monotouch-test commands + +**Trainer:** SkillTrainer | **Skill:** macios-binding-creator | **Trigger:** Deeper analysis of copilot session 62c564f6-99e3-47f5-b523-d206c665b71d (ARKit bindings, turns 12-14 + test workflow) + +### Assessment + +**Source:** Session 62c564f6 turns 12-14 — User guided agent through architectural cleanup of ARKit's `frameworks.sources` organization. Agent created `ARKIT_C_API_SOURCES` + `MACOS_DOTNET_SOURCES +=` hack; user showed cleaner approach of guarding entire bgen file with `#if !__MACOS__` and adding framework to `MACOS_FRAMEWORKS`. Also turn 11: user had to remind agent to delete empty `.todo` file. + +**Issues found (ranked):** +1. ❌ **Missing: Mixed API surface framework pattern** — When a framework has ObjC APIs on mobile and C APIs on macOS, the agent created split source lists instead of using `#if` guards on the bgen file. This is a key architectural pattern not documented. +2. ❌ **Wrong: Monotouch-test commands** — Skill said `make -C tests/monotouch-test run` which doesn't work. Correct commands are per-platform from `tests/monotouch-test/dotnet/{Platform}/`. User had to ask about macOS target (turn 3-4) and explicitly request per-platform runs (turns 6, 9). +3. ⚠️ **Weak: Empty .todo file cleanup** — Agent forgot despite existing ⚠️ guidance. Needs upgrade to ❌ level. + +### Cycle 1: Add mixed framework pattern + strengthen .todo cleanup + +**Hypothesis:** Documenting the "guard entire bgen file with `#if !__MACOS__`" pattern will prevent agents from creating convoluted split source lists. Upgrading .todo cleanup to ❌ level will make it harder to miss. + +**Edits:** +1. `references/binding-patterns.md` — New "Frameworks with Mixed API Surfaces (ObjC + C)" subsection with 4-step pattern, code examples (frameworks.sources, bgen file guard, manual C API guard), and anti-pattern against split source lists. +2. `SKILL.md` — Upgraded empty `.todo` file deletion from ⚠️ to ❌ with stronger language. +3. `SKILL.md` Step 6d — Replaced wrong `make -C tests/monotouch-test run` with per-platform commands (iOS, tvOS, macOS, MacCatalyst) including `run-bare` for desktop platforms and casing warning. +4. `references/test-workflow.md` — Rewrote Monotouch Tests section with per-platform command table, `run-bare` guidance for desktop, note that mlaunch is NOT needed (unlike introspection), and casing requirements. + +**Outcome:** ✅ Changes kept. + +### Patterns Learned +- **Guard the bgen file, not the source list** — When ObjC API definitions won't compile on a platform (UIKit dependencies on macOS), wrap the entire bgen file in `#if !__MACOS__` rather than splitting source lists. This is simpler and more maintainable. +- **Severity matters for agent compliance** — The ⚠️ level for .todo cleanup wasn't enough; the agent skipped it. ❌ level rules get followed more consistently. +- **Per-platform monotouch-test commands** — `make -C tests/monotouch-test run` doesn't exist. Must use `tests/monotouch-test/dotnet/{Platform}/` with exact casing. Desktop uses `run-bare`, mobile uses `run` (no mlaunch needed unlike introspection). + +### Open Items +- None. + +--- + ## Session: 2026-04-09 (3) — Version determination and enum member availability **Trainer:** SkillTrainer | **Skill:** macios-binding-creator | **Trigger:** User request to enhance skill using copilot session d8792953-287f-485e-aed6-d4a6d46043c8 (SystemConfiguration bindings) diff --git a/.agents/skills/macios-binding-creator/SKILL.md b/.agents/skills/macios-binding-creator/SKILL.md index 4995cef76478..93a7105354f4 100644 --- a/.agents/skills/macios-binding-creator/SKILL.md +++ b/.agents/skills/macios-binding-creator/SKILL.md @@ -236,7 +236,7 @@ make -C tests/xtro-sharpie run-maccatalyst Verify all `.todo` entries for the bound framework are resolved. If any remain, they need binding or explicit `.ignore` entries with justification. -> ⚠️ **Delete empty `.todo` files** after resolving all entries: `git rm tests/xtro-sharpie/api-annotations-dotnet/{platform}-{Framework}.todo`. Do not leave empty `.todo` files in the repository. +> ❌ **ALWAYS delete empty `.todo` files** after resolving all entries: `git rm tests/xtro-sharpie/api-annotations-dotnet/{platform}-{Framework}.todo`. Do not leave empty `.todo` files in the repository — they cause xtro test noise. #### 6b. Cecil Tests @@ -302,10 +302,26 @@ Tests run: X Passed: X Inconclusive: X Failed: X Ignored: X Skip this step if no monotouch-test files were added or modified. +Run per-platform, using **exact casing** for platform names: + ```bash -make -C tests/monotouch-test run +# iOS +make -C tests/monotouch-test/dotnet/iOS run + +# tvOS +make -C tests/monotouch-test/dotnet/tvOS run + +# macOS (use run-bare for captured output) +make -C tests/monotouch-test/dotnet/macOS run-bare + +# MacCatalyst (use run-bare for captured output) +make -C tests/monotouch-test/dotnet/MacCatalyst run-bare ``` +> ⚠️ **Platform casing matters**: Use `iOS`, `tvOS`, `macOS`, `MacCatalyst` exactly — not `ios`, `macos`, etc. + +> ⚠️ **Desktop platforms**: Use `run-bare` (not `run`) for macOS and MacCatalyst — same reason as introspection: `run` launches without capturing stdout. `run-bare` is only available for desktop platforms. + ### Step 7: Handle Test Failures If introspection tests fail for newly bound types: diff --git a/.agents/skills/macios-binding-creator/references/binding-patterns.md b/.agents/skills/macios-binding-creator/references/binding-patterns.md index 95e4bd678abe..a90e31f33532 100644 --- a/.agents/skills/macios-binding-creator/references/binding-patterns.md +++ b/.agents/skills/macios-binding-creator/references/binding-patterns.md @@ -488,6 +488,47 @@ Available preprocessor symbols: > ❌ **NEVER** use platform-specific source file entries (e.g., `FRAMEWORKNAME_MACOS_DOTNET_SOURCES`) for conditional compilation. Use `#if` directives instead — they keep the code in shared files and are the established convention across the codebase. +### Frameworks with Mixed API Surfaces (ObjC + C) + +Some frameworks have Objective-C APIs on mobile platforms (iOS, tvOS, MacCatalyst) but C APIs on macOS — for example, ARKit has ObjC classes on iOS but C-level session/object APIs on macOS. + +**The pattern:** + +1. Add the framework to `MACOS_FRAMEWORKS` in `src/frameworks.sources` — this tells the build system to compile it for macOS +2. Guard the **entire bgen file** (`src/frameworkname.cs`) with `#if !__MACOS__` / `#endif` — the ObjC API definitions have UIKit/AVFoundation dependencies that won't compile on macOS +3. Put macOS-specific C API bindings in `src/FrameworkName/*.cs` guarded with `#if MONOMAC` or `#if __MACOS__` +4. Keep everything in one `FRAMEWORKNAME_SOURCES` list — no split source lists needed + +``` +# In src/frameworks.sources — single unified list: +ARKIT_SOURCES = \ + ARKit/ARObject.cs \ + ARKit/ARSession.cs \ + ARKit/AREnums.cs \ +``` + +```csharp +// src/arkit.cs — entire file guarded for non-macOS +#if !__MACOS__ +using System; +using Foundation; +using UIKit; +// ... all ObjC API definitions ... +#endif // !__MACOS__ +``` + +```csharp +// src/ARKit/ARSession.cs — macOS C API manual binding +#if MONOMAC +[SupportedOSPlatform ("macos26.5")] +public class ARSession : ARObject { + // C API P/Invokes and wrappers +} +#endif // MONOMAC +``` + +> ❌ **NEVER** create separate source file lists (e.g., `ARKIT_C_API_SOURCES`) and append them with `MACOS_DOTNET_SOURCES += $(ARKIT_C_API_SOURCES)`. This creates a maintenance burden. Add the framework to `MACOS_FRAMEWORKS` and use `#if` guards instead. + ## Struct Array Parameter Binding When an Objective-C API takes a C struct pointer + count (e.g., `MyStruct*` + `NSUInteger`), create a manual public wrapper that marshals a managed array to/from the native pointer. This is a common Apple API pattern (MapKit, CarPlay, ARKit, etc.). diff --git a/.agents/skills/macios-binding-creator/references/test-workflow.md b/.agents/skills/macios-binding-creator/references/test-workflow.md index d9e2b90d5d8e..d14f06a0823c 100644 --- a/.agents/skills/macios-binding-creator/references/test-workflow.md +++ b/.agents/skills/macios-binding-creator/references/test-workflow.md @@ -151,14 +151,28 @@ When adding exclusions for types that crash on simulator: ## Monotouch Tests -For manually bound APIs (P/Invokes, manual properties), run the monotouch-test suite: +For manually bound APIs (P/Invokes, manual properties), run the monotouch-test suite per-platform. -```bash -# Build and run monotouch-tests (uses simulator) -make -C tests/monotouch-test run -``` +**Platform casing matters** — use `iOS`, `tvOS`, `macOS`, `MacCatalyst` exactly. + +### Per-Platform Commands + +| Platform | Build | Run | +|----------|-------|-----| +| iOS | `make -C .../dotnet/iOS build` | `make -C .../dotnet/iOS run` | +| tvOS | `make -C .../dotnet/tvOS build` | `make -C .../dotnet/tvOS run` | +| macOS | `make -C .../dotnet/macOS build` | `make -C .../dotnet/macOS run-bare` | +| MacCatalyst | `make -C .../dotnet/MacCatalyst build` | `make -C .../dotnet/MacCatalyst run-bare` | + +Where `...` = `tests/monotouch-test`. + +Alternatively, from the parent directory: `make -C tests/monotouch-test/dotnet run-iOS`, `run-tvOS`, `run-macOS`, `run-MacCatalyst`. + +> ⚠️ **Desktop platforms (macOS, MacCatalyst)**: Use `run-bare` for captured test output — same as introspection. `run` launches the app via `dotnet build -t:Run` which doesn't capture stdout. + +> ⚠️ **`run-bare` is desktop-only.** iOS and tvOS use the simulator via `dotnet build -t:Run` with `SIMCTL_CHILD_NUNIT_AUTOSTART=true` and `SIMCTL_CHILD_NUNIT_AUTOEXIT=true` environment variables (set automatically by the shared Makefile). No manual mlaunch invocation is needed for monotouch-tests — unlike introspection. -Or run specific test fixtures: +### Running Specific Test Fixtures ```bash # Run via dotnet test with a filter