Skip to content

Build Apple libSkiaSharp/libHarfBuzzSharp from GN; delete all Xcode projects#4147

Draft
mattleibow wants to merge 11 commits into
mainfrom
mattleibow/apple-gn-feasibility
Draft

Build Apple libSkiaSharp/libHarfBuzzSharp from GN; delete all Xcode projects#4147
mattleibow wants to merge 11 commits into
mainfrom
mattleibow/apple-gn-feasibility

Conversation

@mattleibow

Copy link
Copy Markdown
Contributor

What

Replaces the hand-maintained Apple Xcode projects with the existing skiasharp_build GN targets, so every platform — Windows, Linux, WASM, Android, macOS, iOS, tvOS, Mac Catalyst — now builds libSkiaSharp/libHarfBuzzSharp from one source of truth in externals/skia/BUILD.gn. The C API source list, feature defines and warning flags live in exactly one place (plus Tizen's project_def.prop).

Deletes all 6 *.xcodeproj bundles + their Info.plist/.h stubs and scripts/infra/native/apple/xcode.cake (−3372 lines).

How

  • macOS ships a plain .dylib straight from GN/ninja (lipo'd fat, @rpath/lib<N>.dylib).
  • iOS / tvOS / Mac Catalyst: GN itself now emits a complete single-arch lib<Name>.framework in its out dir. The skiasharp_build template (in gn/BUILDCONFIG.gn), when skiasharp_apple_framework=true, links the dylib under an internal *_dylib target and runs a GN action (gn/skiasharp/assemble_apple_framework.{py,sh}, first-party Apple CLIs only) that lays out the bundle, sets the framework-relative install_name at link time, thins GN's force-added arm64e slice, and writes the provenance-complete Info.plist (CFBundle* + DT*/BuildMachineOSBuild keys that App Store / notarization asset validation expects).
  • scripts/infra/native/apple/apple.cake's CombineFrameworks then only cp -Rs the base (device-SDK) framework, lipos the per-arch slices' binaries together, and strips + ad-hoc codesigns last. cake owns nothing about bundle shape, install_name or plist.

Drop-in equivalence (verified, clean output/native)

All 4 Apple platforms × both libs:

  • 887 sk_/gr_ symbols, 560 hb_ symbols (byte-identical export set)
  • fat x86_64 arm64; framework-relative install_name (/Versions/A/ for Catalyst)
  • valid ad-hoc codesign
  • Info.plist key set identical to released 3.119.0; Catalyst UIDeviceFamily [2] + LSMinimumSystemVersion 10.15

Runtime tests: iOS simulator 5517 passed / 0 failed, Mac Catalyst 5517 passed / 0 failed.

Notes

  • Xcode Command Line Tools are still required on macOS agents for the SDK + clang toolchain GN drives, but the build is pure gn/ninja — no Xcode project files.
  • Submodule changes are on mono/skia branch dev/apple-gn-dylib-link and must merge first (the externals/skia pointer here bumps to it).
  • Based on mattleibow/move-capi-to-libskiasharp (Compile C API (src/c) in libSkiaSharp instead of upstream :core #4131); retarget to main after that merges.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

mattleibow and others added 11 commits June 11, 2026 13:17
Follow-up to the mono/skia change that moves the fork-owned C API shim
(src/c/*.cpp) out of upstream Skia's :core target and into the libSkiaSharp
build, decoupling it from :core's milestone-churning transitive dependencies
(which silently dropped SK_FONTMGR_FONTCONFIG_AVAILABLE on Linux in m148).

On platforms built via the GN skiasharp_build("SkiaSharp") target (Android,
Linux, Windows, WASM) the move is handled entirely in the submodule. But the
Apple platforms (macOS, iOS, tvOS) and Tizen build libSkiaSharp from a project
file that compiles the managed-interop glue and links the GN-built libskia.a;
they relied on src/c being inside :core. Add the src/c/*.cpp sources to:

  - native/{macos,ios,tvos}/libSkiaSharp/libSkiaSharp.xcodeproj
  - native/tizen/libSkiaSharp/project_def.prop

so every platform compiles the C API into libSkiaSharp itself, consistent with
how the src/xamarin glue is already handled. These projects already link
-lskottie/-lsksg/-lskresources/-ljsonreader, so no extra linkage is needed.

Validated: macOS arm64 (real dylib, full test suite passes) and Linux x64
(real .so with libfontconfig.so.1 dependency and 44 Fc* imports — the m148
regression guard).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The macOS/iOS/tvOS libSkiaSharp targets build via xcodebuild (pbxproj), not
GN, so the GN-side deprecation guardrail does not cover them. They compiled
the deprecated SkPathOps TightBounds() with only a warning. Add
OTHER_CFLAGS = -Werror=deprecated-declarations to every build configuration in
the three Apple xcodeprojs so deprecated Skia API usage fails the build on
Apple platforms too, matching the GN (:skiasharp_strict) and Tizen (-Werror)
builds. These targets compile only our src/c + src/xamarin shim (upstream Skia
is linked as a prebuilt static lib), so the scope is exactly our own sources.

Bumps externals/skia to pick up the TightBounds fix + GN warnings guardrail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stage 1 of migrating the Apple native build off hand-maintained Xcode
projects onto the existing GN skiasharp_build("SkiaSharp") target (as
Linux/Windows/WASM/Android already do). The macOS libSkiaSharp task now
builds the full SkiaSharp GN target per arch and lipos the resulting
dylibs into output/native/osx/libSkiaSharp.dylib, dropping xcodebuild.

Verified drop-in: exported sk_* symbols byte-identical to the xcodebuild
baseline (1064==1064), imports a strict subset, identical install_name/
arch/minos/Metal linkage; full test suite passes (5666/0). Bumps the skia
submodule to include the GN Apple dylib-link ldflag fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… (Stage 2+3)

Redirect the iOS and tvOS GnNinja invocations from the upstream
"skia modules/skottie" target to the full "SkiaSharp" shared_library
target, so ninja links libSkiaSharp.dylib directly (as Linux/Windows/WASM
already do) instead of compiling only the shim in a hand-maintained xcodeproj.

Adds a CreateFrameworkFromDylibs cake helper that wraps the GN-produced
per-arch dylibs into the .framework bundle xcodebuild used to produce:
lipo the slices, rewrite the framework-relative @rpath install_name, write a
deterministic Info.plist (stable contract keys only), ad-hoc codesign, and
lay out the versioned bundle + zip for MacCatalyst. EnsureSingleArch thins the
GN device/Catalyst arm64 dylib down from arm64+arm64e to the shipped arm64.

MacCatalyst reuses native/ios/build.cake, so no separate xcodeproj exists.

Verified drop-in vs xcodebuild/released baselines per arch: exported sk_*/gr_*
symbols identical (887 per slice), install_name, archs, Mach-O type, per-slice
platform tags (iOS device VERSION_MIN, sim arm64 BUILD_VERSION platform 7/8,
MacCatalyst platform 6 minos 13.1/14.0) and framework/plist layout all match.

libHarfBuzzSharp still builds via xcodebuild on Apple (Stage 4).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Apple native build

Stage 4 of the Apple GN migration. libHarfBuzzSharp on macOS/iOS/tvOS/MacCatalyst
now builds from the skiasharp_build("HarfBuzzSharp") GN target (via GnNinja) and is
wrapped into a .framework by the shared CreateFrameworkFromDylibs helper, exactly like
libSkiaSharp. This introduces the first GN/ninja step for HarfBuzz on Apple (it was
previously compiled inside the xcodeproj with no ninja step).

With this, no Apple native library uses xcodebuild any more:
- Delete the iOS + tvOS libHarfBuzzSharp xcodeprojs (macOS HB xcodeproj already removed).
- Remove the now-dead RunXCodeBuild / CreateFatDylib / CreateFatFramework /
  CreateFatVersionedFramework / SafeCopy helpers and the #addin Cake.XCode directive.
- Remove the unused RunLipo(DirectoryPath,...) overload.
- Rename scripts/infra/native/apple/xcode.cake -> apple.cake (no longer wraps Xcode).

Equivalence verified per platform/arch against the xcodebuild / released-NuGet baselines:
all 560 hb_* exported symbols identical, install_name, archs, and platform/minos tags
match. The only symbol delta is two Xcode apple-generic versioning vanity globals
(libHarfBuzzSharpVersionNumber/String) that no P/Invoke references and that the macOS
variant never had — not part of the ABI contract.

Docs: add update-skia known-gotcha #23 documenting that native sources/defines now live
solely in BUILD.gn for every platform including Apple (no xcodeproj Sources phase to mirror).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… references)

Rewrite update-skia known-gotcha #23 to describe the current single-source-of-truth GN
build and what to DO (add C API sources to the skiasharp_build target + Tizen prop), instead
of telling readers not to touch files that no longer exist. Scrub the now-confusing
'replaces the old xcodebuild framework' / 'matching the old xcodeproj output' references from
apple.cake and the macOS/iOS/tvOS build.cake comments so they describe what the code does, not
what it replaced.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nstall_name

Apple .framework bundles produced by the GN migration now carry the full
Xcode build-provenance metadata (DTPlatformName/DTSDKName/DTSDKBuild/
DTPlatformBuild/DTPlatformVersion/DTXcode/DTXcodeBuild/DTCompiler/
BuildMachineOSBuild) that released frameworks have, so they validate for
App Store submission and notarization as a true drop-in replacement.

- Add make-framework-plist.sh: generates the framework Info.plist using
  only first-party Apple tools (xcrun/xcodebuild/sw_vers for provenance,
  plutil to write the binary plist), matching the released key set exactly.
- apple.cake: WriteFrameworkPlist now delegates to that script; drop the
  install_name_tool surgery in CreateFrameworkFromDylibs since the GN
  submodule now sets the framework install_name at link time.
- ios/tvos build.cake: pass skiasharp_apple_framework(+_versioned for
  Mac Catalyst) GN args.
- Bump externals/skia submodule.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GN now emits the complete single-arch lib<Name>.framework in its out dir, so
the Apple cake glue collapses to fusing the per-arch frameworks and signing.

- apple.cake: replace the dylib-based builder (CreateFrameworkFromDylibs,
  WriteFrameworkPlist, EnsureSingleArch, SupportedPlatformToSdk) with
  CombineFrameworks: cp -R the base (device-SDK) framework, lipo every slice's
  binary into it, then strip + ad-hoc codesign last (lipo invalidates the
  signature), and zip the versioned Mac Catalyst bundle. The bundle layout,
  install_name and provenance Info.plist now come straight from GN.
- native/ios + native/tvos build.cake: GnNinja the framework target per arch
  (skiasharp_apple_framework=true, _versioned=true for Mac Catalyst) and pass
  the returned out-dir framework dirs to CombineFrameworks.
- Delete make-framework-plist.sh: the plist is generated inside the GN build.
- Bump externals/skia to the submodule commit that emits the framework.

macOS is unchanged (plain dylib path, args stay false).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

📦 Try the packages from this PR

Warning

Do not run these scripts without first reviewing the code in this PR.

Step 1 — Download the packages

bash / macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 4147

PowerShell / Windows:

iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 4147"

Step 2 — Add the local NuGet source

dotnet nuget add source ~/.skiasharp/hives/pr-4147/packages --name skiasharp-pr-4147
More options
Option Description
--successful-only / -SuccessfulOnly Only use successful builds
--force / -Force Overwrite previously downloaded packages
--list / -List List available artifacts without downloading
--build-id ID / -BuildId ID Download from a specific build

Or download manually from Azure Pipelines — look for the nuget artifact on the build for this PR.

Remove the source when you're done:

dotnet nuget remove source skiasharp-pr-4147

@github-actions

Copy link
Copy Markdown
Contributor

📖 Documentation Preview

The documentation for this PR has been deployed and is available at:

🔗 View Staging Site
🔗 View Staging Docs
🔗 View Staging Gallery (Blazor)
🔗 View Staging Gallery (Uno Platform)
🔗 View Staging SkiaFiddle

This preview will be updated automatically when you push new commits to this PR.


This comment is automatically updated by the documentation staging workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant