Skip to content

tvOS native build missing arm64 simulator slice and device/simulator split #3555

@mattleibow

Description

@mattleibow

Summary

The tvOS native build script (native/tvos/build.cake) creates a single fat framework combining device (arm64) and simulator (x86_64) slices into one output/native/tvos/ directory. This differs from the iOS build which correctly separates into ios/ (device) and iossimulator/ (simulator) directories. This causes two problems:

  1. Missing arm64 simulator build — Apple Silicon Macs run tvOS simulators natively as arm64, but the build only produces an x86_64 simulator slice
  2. Mixed device/simulator fat binary — Xcode 16+ linkers reject this: ld: building for 'tvOS-simulator', but linking in dylib built for 'tvOS'

Current Behavior

tvOS build (native/tvos/build.cake)

// Only 2 builds — missing arm64 simulator
Build("appletvsimulator", "x86_64", "x64");
Build("appletvos", "arm64", "arm64");

// Single output directory — mixes device + simulator
DirectoryPath OUTPUT_PATH = MakeAbsolute(ROOT_PATH.Combine("output/native/tvos"));
CreateFatFramework(OUTPUT_PATH.Combine("libSkiaSharp"));

Result: output/native/tvos/libSkiaSharp.framework — fat binary with x86_64 (simulator) + arm64 (device) combined, tagged with LC_VERSION_MIN_TVOS (ambiguous platform).

iOS build (native/ios/build.cake) — correct pattern

// 3 builds — includes arm64 simulator
Build("iphonesimulator", "x86_64", "x64");
Build("iphonesimulator", "arm64", "arm64");
Build("iphoneos", "arm64", "arm64");

// Separate output directories
CreateFatFramework(OUTPUT_PATH.Combine("ios/libSkiaSharp"));           // device only
CreateFatFramework(OUTPUT_PATH.Combine("iossimulator/libSkiaSharp"));  // simulator only

Result: Two directories with properly tagged binaries.

IncludeNativeAssets.SkiaSharp.targets comparison

iOS correctly distinguishes simulator/device:

<ItemGroup Condition="$(TargetFramework.Contains('-ios')) and '$(RuntimeIdentifier)' != ''">
    <NativeReference Include="...\iossimulator\libSkiaSharp.framework" Condition="$(RuntimeIdentifier.StartsWith('iossimulator'))" />
    <NativeReference Include="...\ios\libSkiaSharp.framework" Condition="!$(RuntimeIdentifier.StartsWith('iossimulator'))" />
</ItemGroup>

tvOS does not:

<ItemGroup Condition="$(TargetFramework.Contains('-tvos'))">
    <NativeReference Include="...\tvos\libSkiaSharp.framework" />
</ItemGroup>

Expected Behavior

The tvOS build should match the iOS pattern:

native/tvos/build.cake

DirectoryPath OUTPUT_PATH = MakeAbsolute(ROOT_PATH.Combine("output/native"));

Build("appletvsimulator", "x86_64", "x64");     // simulator x86_64 (Intel Macs)
Build("appletvsimulator", "arm64", "arm64");     // simulator arm64 (Apple Silicon Macs) — NEW
Build("appletvos", "arm64", "arm64");            // device arm64

CreateFatFramework(OUTPUT_PATH.Combine("tvos/libSkiaSharp"));           // device only
CreateFatFramework(OUTPUT_PATH.Combine("tvossimulator/libSkiaSharp"));  // simulator only

IncludeNativeAssets.SkiaSharp.targets and IncludeNativeAssets.HarfBuzzSharp.targets

<ItemGroup Condition="$(TargetFramework.Contains('-tvos')) and '$(RuntimeIdentifier)' != ''">
    <NativeReference Include="...\tvossimulator\..." Condition="$(RuntimeIdentifier.StartsWith('tvossimulator'))" />
    <NativeReference Include="...\tvos\..." Condition="!$(RuntimeIdentifier.StartsWith('tvossimulator'))" />
</ItemGroup>

Same changes needed for libHarfBuzzSharp in both files.

Reproduction

On an Apple Silicon Mac with Xcode 16+:

dotnet cake --target=externals-download
dotnet build samples/Basic/tvOS/SkiaSharpSample/SkiaSharpSample.csproj \
  -f net8.0-tvos -p:IsNetTVOSSupported=true

Error:

ld: building for 'tvOS-simulator', but linking in dylib (.../output/native/tvos/libSkiaSharp.framework/libSkiaSharp) built for 'tvOS'
clang++: error: linker command failed with exit code 1

Workaround

Re-tag the binary platform with vtool:

mkdir -p output/native/tvossimulator/libSkiaSharp.framework
lipo output/native/tvos/libSkiaSharp.framework/libSkiaSharp -thin arm64 -output /tmp/tvos_arm64.dylib
vtool -set-build-version 8 11.0 17.5 -replace -output /tmp/tvossim_arm64.dylib /tmp/tvos_arm64.dylib
# platform 8 = TVOSSIMULATOR
cp /tmp/tvossim_arm64.dylib output/native/tvossimulator/libSkiaSharp.framework/libSkiaSharp
codesign -f -s - output/native/tvossimulator/libSkiaSharp.framework/libSkiaSharp

Files to Change

  1. native/tvos/build.cake — Add arm64 simulator build, split output into tvos/ and tvossimulator/
  2. binding/IncludeNativeAssets.SkiaSharp.targets — Add RuntimeIdentifier-based tvos/tvossimulator split
  3. binding/IncludeNativeAssets.HarfBuzzSharp.targets — Same split
  4. scripts/azure-templates-stages-native-macos.yml — May need updating if CI artifact paths change

Environment

  • macOS on Apple Silicon (arm64)
  • Xcode 16+ (strict linker platform checking)
  • .NET 8 with tvOS workload
  • Current: only x86_64 simulator + arm64 device in single fat binary
  • Needed: x86_64 simulator + arm64 simulator + arm64 device in separate directories

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions