Skip to content

[msbuild] Embed on-demand resource asset packs in the app bundle for the simulator. Fixes #26218 - #26481

Open
rolfbjarne wants to merge 5 commits into
mainfrom
dev/rolf/sim-odr
Open

[msbuild] Embed on-demand resource asset packs in the app bundle for the simulator. Fixes #26218#26481
rolfbjarne wants to merge 5 commits into
mainfrom
dev/rolf/sim-odr

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

On-demand resources (ODR) didn't work on the simulator. The build creates the asset packs next to the app bundle ($(DeviceSpecificOutputPath)OnDemandResources/*.assetpack) and writes an AssetPackManifestTemplate.plist that points at http://127.0.0.1 dev-hosting URLs. This works on device (where the IDE hosts the packs over http), but there's no such hosting server for the simulator, so NSBundleResourceRequest fails at runtime with "No manifest found for bundleID".

Xcode handles this with its "Embed Asset Packs In Product Bundle" setting, which copies the asset packs into the app bundle and rewrites the manifest to point at the embedded (non-streamable) packs. This PR does the same: a new _EmbedOnDemandResources target (wired into _CreateAssetPackManifestMobile) that, for simulator builds, copies the asset packs into <app>/OnDemandResources, rewrites AssetPackManifestTemplate.plist into AssetPackManifest.plist with relative (non-streamable) URLs, and removes the streaming template. This mirrors the existing embed logic used for AdHoc (IPA) builds (_PackageOnDemandResources), including gating the steps on the intermediate OnDemandResources directory so it also works on Windows (Pair to Mac) builds.

Testing: a new test project (AppWithOnDemandResources) — a small UIKit app that tries to access its tagged on-demand resource at runtime and turns the screen green on success, red on failure (yellow while loading) — and a new build test (OnDemandResourcesTest) that verifies the simulator app bundle contains the embedded asset pack, a valid OnDemandResources.plist and AssetPackManifest.plist, and no leftover streaming AssetPackManifestTemplate.plist.

Fixes #26218

🤖 Pull request created by Copilot

rolfbjarne and others added 3 commits August 27, 2026 18:44
…the simulator

On-demand resources (ODR) don't work on the simulator: the build creates the
asset packs next to the app bundle (in the OnDemandResources directory in the
intermediate output directory) and writes an AssetPackManifestTemplate.plist
that points at http://127.0.0.1 dev-hosting URLs. This works on device (where
the IDE hosts the packs over http), but there's no such hosting server for the
simulator, so NSBundleResourceRequest fails at runtime with "No manifest found
for bundleID".

Xcode handles this with its "Embed Asset Packs In Product Bundle" setting,
which copies the asset packs into the app bundle and rewrites the manifest to
point at the embedded (non-streamable) packs. Do the same here: add a new
_EmbedOnDemandResources target that, for simulator builds, copies the asset
packs into <app>/OnDemandResources, rewrites AssetPackManifestTemplate.plist
into AssetPackManifest.plist with relative (non-streamable) URLs, and removes
the streaming template. This mirrors the existing embed logic used for AdHoc
(IPA) builds.

Also add a new test project (AppWithOnDemandResources) and a test
(OnDemandResourcesTest) that builds a simulator app with a tagged
BundleResource and verifies the app bundle contains the embedded asset pack,
a valid OnDemandResources.plist and AssetPackManifest.plist, and no leftover
streaming AssetPackManifestTemplate.plist.

Fixes #26218

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: be0c22c8-907d-407c-ba83-a262132246f6
…runtime

Turn the test app into a real UIKit app that tries to access the on-demand
resource tagged "MusicTag" (the SoundBank.bin file). The screen is yellow while
the request is in progress, turns green if the resource could be accessed and
read, and red if anything went wrong. This makes it easy to verify at runtime
(in particular on the simulator) that on-demand resources actually work.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: be0c22c8-907d-407c-ba83-a262132246f6
…he app bundle

The WriteAssetPackManifest and Delete steps in _EmbedOnDemandResources gated on
Exists(...) of a file inside the app bundle ($(AppBundleDir)). On a Windows
(Pair to Mac) build the app bundle is produced on the Mac and isn't surfaced
back to the build host, so these conditions (evaluated on the build host) would
always be false and silently skip rewriting/removing the streaming manifest
template, leaving the http://127.0.0.1 URLs that don't work on the simulator.

Gate all three steps on the intermediate '$(DeviceSpecificOutputPath)OnDemandResources'
directory instead (where the asset packs are compiled), which is what the
existing _PackageOnDemandResources target does.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: be0c22c8-907d-407c-ba83-a262132246f6
Copilot AI lite review requested due to automatic review settings August 27, 2026 21:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes on-demand resources (ODR) on simulator builds by embedding compiled asset packs into the app bundle and rewriting the asset pack manifest to use relative (non-streamable) URLs, matching Xcode’s “Embed Asset Packs In Product Bundle” behavior. It also adds a new runtime-focused test app and a unit test to validate the resulting bundle structure and manifests.

Changes:

  • Add an MSBuild target (_EmbedOnDemandResources) to copy OnDemandResources/*.assetpack into <app>.app/OnDemandResources for simulator builds and generate AssetPackManifest.plist (removing the streaming template).
  • Wire embedding into the .NET SDK build flow via _CreateAssetPackManifestMobile.
  • Add AppWithOnDemandResources plus OnDemandResourcesTest to validate embedded packs and manifests for iOS/tvOS simulators.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/dotnet/UnitTests/OnDemandResourcesTest.cs New unit test that builds a test app and asserts embedded asset packs + rewritten manifest for simulator builds.
tests/dotnet/AppWithOnDemandResources/AppDelegate.cs Runtime smoke app that requests an ODR tag and visually reports success/failure.
tests/dotnet/AppWithOnDemandResources/Main.cs App entry point for the ODR test app.
tests/dotnet/AppWithOnDemandResources/shared.csproj Shared test app project settings and a tagged BundleResource to force asset pack creation.
tests/dotnet/AppWithOnDemandResources/SoundBank.bin Test payload file to be packaged as an on-demand resource.
tests/dotnet/AppWithOnDemandResources/Makefile Test project build integration.
tests/dotnet/AppWithOnDemandResources/shared.mk Shared make include for platform-specific builds.
tests/dotnet/AppWithOnDemandResources/iOS/Makefile iOS make include for the test app.
tests/dotnet/AppWithOnDemandResources/iOS/AppWithOnDemandResources.csproj iOS tfm wrapper project importing shared settings.
tests/dotnet/AppWithOnDemandResources/tvOS/Makefile tvOS make include for the test app.
tests/dotnet/AppWithOnDemandResources/tvOS/AppWithOnDemandResources.csproj tvOS tfm wrapper project importing shared settings.
msbuild/Xamarin.Shared/Xamarin.iOS.Common.targets Adds _EmbedOnDemandResources target to copy packs + rewrite manifest for simulator builds.
dotnet/targets/Xamarin.Shared.Sdk.targets Ensures the embedding runs as part of _CreateAssetPackManifestMobile for non-macOS platforms.
Suppressed comments (1)

tests/dotnet/UnitTests/OnDemandResourcesTest.cs:66

  • 🤖 ❌ Nullable — Avoid the null-forgiving operator (!) in the manifest parsing as well; use explicit null checks so the test both fails clearly and stays compliant with the repo’s nullable rules.
			var manifest = PDictionary.OpenFile (manifestPath);
			var resources = manifest!.GetArray ("resources");
			Assert.That (resources, Is.Not.Null.And.Not.Empty, "AssetPackManifest.plist must contain resources.");
			foreach (var resource in resources!.OfType<PDictionary> ()) {
				var url = resource.Get<PString> ("URL")?.Value;

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/dotnet/UnitTests/OnDemandResourcesTest.cs
Comment thread tests/dotnet/AppWithOnDemandResources/AppDelegate.cs Outdated
Comment thread msbuild/Xamarin.Shared/Xamarin.iOS.Common.targets
@vs-mobiletools-engineering-service2

This comment has been minimized.

Address PR review feedback:

- Split the embedding into a cheap always-run part (delete the streaming
  'AssetPackManifestTemplate.plist' that '_CreateAssetPackManifest'
  regenerates every build) and an incremental part
  ('_CopyOnDemandResourcesToAppBundle') keyed on the compiled asset packs.
  This avoids rewriting 'AssetPackManifest.plist' on every incremental
  build, which was forcing a re-codesign.
- Remove/recreate the embedded 'OnDemandResources' directory before copying
  so stale asset packs (removed or renamed since the last build) don't
  linger, mirroring '_PackageOnDemandResources'.
- Collect the incremental inputs in a dependency target rather than in the
  'Inputs' attribute: a target's 'Inputs' doesn't expand recursive '**'
  wildcards (it would treat 'OnDemandResources/**/*' as a literal missing
  file and always run), and a static ItemGroup wouldn't see the packs
  because they're generated during the build.
- Fix the test's nullable handling (drop the '!' null-forgiving operator in
  favour of an 'AssertNotNull' helper) and dispose the 'NSData' returned by
  'NSData.FromFile' in the test app.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: be0c22c8-907d-407c-ba83-a262132246f6
@rolfbjarne

Copy link
Copy Markdown
Member Author

Addressed the review comments in fc56021:

  • Incremental builds (@rolfbjarne): the embedding is now split into a cheap always-run part (deleting the streaming AssetPackManifestTemplate.plist that _CreateAssetPackManifest regenerates every build) and an incremental _CopyOnDemandResourcesToAppBundle target keyed on the compiled asset packs. Previously the manifest was rewritten on every incremental build, forcing a re-codesign. Verified via a double-build: on build 2 the copy target is now skipped ("up-to-date"), AssetPackManifest.plist's mtime is stable, and no re-codesign is triggered.
    • Note on the input glob: a target's Inputs attribute does not expand recursive ** wildcards (it treats OnDemandResources/**/* as a literal missing file and always runs), and a static ItemGroup wouldn't see the packs because they're generated during the build - so the inputs are collected in a dependency target. On remote (Pair to Mac) builds $(AppBundleDir) isn't surfaced to the host, so incremental may always run there; that mirrors _PackageOnDemandResources and is an acceptable tradeoff (incremental primarily benefits the local Mac dev loop).
  • Stale asset packs (Copilot): the embedded OnDemandResources directory is now removed and recreated before copying, mirroring _PackageOnDemandResources.
  • Nullable (Copilot): dropped the ! null-forgiving operator in favour of an AssertNotNull helper.
  • Memory management (Copilot): the NSData from NSData.FromFile is now disposed with using.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

All test apps get tests/common/shared-dotnet.plist as a partial app
manifest, and it declares a UISceneDelegateClassName of "SceneDelegate".
AppWithOnDemandResources didn't have such a class, so at launch iOS
printed:

    [SceneConfiguration] Info.plist configuration "Default Configuration" for
    UIWindowSceneSessionRoleApplication contained UISceneDelegateClassName key,
    but could not load class with name "SceneDelegate".

and no scene (and thus no window) was ever created, so the app showed
nothing at all.

Move the UI and the on-demand resource logic into a SceneDelegate, and
leave the AppDelegate with just the scene configuration, the same way
the other test apps do it. Also print the result of the resource request
to the console, so the outcome is visible without looking at the screen.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: 97da63c42da5cc5f6950866d41229fe5a0a0e669 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🔥 [CI Build #97da63c] Test results 🔥

Test results

❌ Tests failed on VSTS: test results

1 tests crashed, 0 tests failed, 200 tests passed.

Failures

❌ Tests on macOS Ventura (13) tests

🔥 Failed catastrophically on VSTS: test results - mac_ventura (no summary found).

Html Report (VSDrops) Download

Successes

✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download
✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 4 tests passed. Html Report (VSDrops) Download
✅ linker (iOS): All 15 tests passed. Html Report (VSDrops) Download
✅ linker (MacCatalyst): All 15 tests passed. Html Report (VSDrops) Download
✅ linker (macOS): All 21 tests passed. Html Report (VSDrops) Download
✅ linker (tvOS): All 15 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 19 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 18 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 21 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 19 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ sharpie: All 1 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

macOS tests

✅ Tests on macOS Monterey (12): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sonoma (14): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sequoia (15): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Tahoe (26): All 5 tests passed. Html Report (VSDrops) Download

Linux Build Verification

Linux build succeeded

Pipeline on Agent
Hash: 97da63c42da5cc5f6950866d41229fe5a0a0e669 [PR build]

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS ODR not working

3 participants