Skip to content

Expose application artifacts#25723

Merged
dalexsoto merged 28 commits into
mainfrom
dev/redth/apple-artifacts-plan
Jun 22, 2026
Merged

Expose application artifacts#25723
dalexsoto merged 28 commits into
mainfrom
dev/redth/apple-artifacts-plan

Conversation

@Redth

@Redth Redth commented Jun 17, 2026

Copy link
Copy Markdown
Member

Apple platform builds and publishes now expose their final outputs through a shared @(ApplicationArtifact) MSBuild item group. This gives custom targets, tooling, and CI a stable, structured way to discover the produced .app, .ipa, .pkg, and .xcarchive outputs instead of parsing individual scalar properties ($(AppBundleDir), $(IpaPackagePath), $(PkgPackagePath), $(ArchiveDir), …). The item group name is platform-neutral, leaving room for other SDKs to reuse the same contract.

What's exposed

  • One @(ApplicationArtifact) item per final artifact, whose item identity is the absolute path to the artifact.
  • Well-known metadata on each item:
    • PackageFormatapp, ipa, pkg, or xcarchive
    • IsDirectorytrue for .app/.xcarchive, false for .ipa/.pkg
    • PlatformNameiOS, tvOS, macOS, or MacCatalyst
    • BundleIdentifier — the resolved app bundle identifier
  • Collected automatically during a normal build: the app bundle after Codesign, the IPA after CreateIpa, the PKG after _CreateInstaller, and the Xcode archive after Archive — so artifacts gated by BuildIpa, CreatePackage, or ArchiveOnBuild are surfaced when those are enabled.

How to query

GetApplicationArtifacts builds the project and returns @(ApplicationArtifact); Publish returns the same group (and enables BuildIpa/CreatePackage as appropriate):

$ dotnet build MyApp.csproj -t:GetApplicationArtifacts -getItem:ApplicationArtifact
$ dotnet build MyApp.csproj -t:Publish -getItem:ApplicationArtifact

Extensibility

  • GetApplicationArtifactsDependsOn is a post-build extension point so later SDK layers (for example .NET MAUI) can enrich the metadata on the platform-created items, or add items for additional artifacts.
  • Build is a hard-coded dependency of GetApplicationArtifacts, outside the overridable property, so overwriting GetApplicationArtifactsDependsOn can never skip platform artifact population. Platform metadata stays limited to the four well-known values above; extension targets own anything richer and should update the platform-created items rather than duplicate them.

Windows (Pair to Mac)

On Windows the .ipa/.xcarchive are produced on the paired Mac and only copied back afterwards, so the collectors are anchored on the public CreateIpa/Archive wrapper targets (which complete after the copy-back) instead of the internal _ZipIpa/_CoreArchive. The archive collector prefers the local $(ArchiveDir) and falls back to the copied-back $(ArchivePath). The .app stays on the Mac during a remote build, so it is intentionally not surfaced there (guarded by IsRemoteBuild); surfacing the Windows .app and the IsAppDistribution IPA is left for a follow-up.

App-extension fix

Adding a Returns attribute (on GetApplicationArtifacts) surfaced a subtle MSBuild behavior: once any target in a .targets file uses Returns, that file's other targets stop returning their Outputs to <MSBuild> callers. That silently emptied the legacy GetAppExtensionBundlePath/BuildAndGetAppExtensionBundlePath getters, so app extensions were no longer embedded into their container app's PlugIns folder. Both targets now declare Returns explicitly (equal to their previous Outputs), restoring extension embedding while keeping the new feature.

Tests

  • A dedicated MySimpleAppWithArtifactMetadata test project (rather than mutating a shared one) exercises the GetApplicationArtifactsDependsOn extension/override behavior so the tests can run in parallel.
  • PostBuildTest item-query tests for .app, .ipa, .pkg, and .xcarchive, extension-added metadata, publish-target augmentation, an overwritten GetApplicationArtifactsDependsOn, and the target dependency graph — asserted both from the binlog and via -getItem.
  • [Category("RemoteWindows")] tests validating that the .ipa and .xcarchive are surfaced after Pair-to-Mac copy-back.

Docs

  • docs/building-apps/build-items.md@(ApplicationArtifact) and its metadata.
  • docs/building-apps/build-properties.mdGetApplicationArtifactsDependsOn.
  • docs/building-apps/build-targets.md — the GetApplicationArtifacts contract and -getItem query examples.

Validation

  • xmllint --noout msbuild/Xamarin.Shared/Xamarin.Shared.targets msbuild/Xamarin.Shared/Xamarin.iOS.Common.targets
  • git diff --check
  • Built the product (make all install) and ran the @(ApplicationArtifact) query tests and the app-extension build tests for iOS/tvOS/macOS: artifacts are populated with absolute paths and correct metadata, and app extensions are embedded into their container app.
  • The RemoteWindows IPA/archive tests run on the Windows CI leg.

Redth and others added 14 commits June 16, 2026 16:16
Add public MSBuild item groups for final MaciOS build and publish artifacts so custom targets and CI can consume app bundles, IPA files, PKG installers, and Xcode archives with metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Collapse MaciOS artifact and published artifact item groups into ApplePackageOutput, matching AndroidPackageOutput naming and reducing metadata to the package contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make ApplePackageOutput Signed reflect app code signing only, and expose separate PackageSigned metadata for .pkg installer package signing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop Signed and PackageSigned from ApplePackageOutput since Apple builds do not emit parallel signed and unsigned package outputs that need to be distinguished.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop AppBundlePath from ApplePackageOutput so the public contract focuses on the final artifact item identity and stable package metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add direct GetApplePackageOutputs coverage using MSBuild item query output for IPA, PKG, app bundle, and xcarchive artifacts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename the public artifact item group and query target to ApplicationArtifact and GetApplicationArtifacts so the surface can be shared across platforms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add GetApplicationArtifactsDependsOn so later SDKs such as MAUI can enrich ApplicationArtifact metadata before GetApplicationArtifacts returns items.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Validate that GetApplicationArtifactsDependsOn extension targets can observe platform-produced ApplicationArtifact items before adding metadata, and verify both app and package artifacts receive the augmented metadata. Restore the injected sample project content after the test to avoid leaving dirty files on failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Route Publish through GetApplicationArtifacts after _PrePublish so targets appended with GetApplicationArtifactsDependsOn can augment ApplicationArtifact metadata before Publish returns items. Add publish-target coverage that validates a MAUI-like extension sees platform artifacts before updating metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document that GetApplicationArtifactsDependsOn extension targets should update platform-produced ApplicationArtifact items when adding metadata, while only adding new items for additional artifacts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make Build a mandatory GetApplicationArtifacts dependency instead of part of the overridable GetApplicationArtifactsDependsOn property. Extension targets still run after platform artifacts are produced, and tests now overwrite the property to prove Build cannot be removed while metadata augmentation still applies to GetApplicationArtifacts and Publish.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update ApplicationArtifact target-result examples to use dotnet build with -getTargetResult for GetApplicationArtifacts and Publish.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve conflict in Xamarin.Shared.targets by keeping the ApplicationArtifact query target and the new PrepareAssemblies targets from main.

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

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@Redth Redth marked this pull request as ready for review June 17, 2026 18:23
@Redth Redth requested a review from mauroa as a code owner June 17, 2026 18:23
Copilot AI review requested due to automatic review settings June 17, 2026 18:23
@Redth Redth requested a review from rolfbjarne as a code owner June 17, 2026 18:23
@Redth

Redth commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Android equivalent: dotnet/android#11674

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

…ationArtifact)

Adding the GetApplicationArtifacts target (with Returns="@(ApplicationArtifact)")
to Xamarin.Shared.targets regressed app-extension embedding: a plain 'dotnet build'
of an app that references an app extension stopped copying the .appex into the
container app's PlugIns folder, failing DotNetProjectTest.BuildProjectsWithExtensions
on iOS, tvOS and macOS.

The cause is an MSBuild behavior: once any target in a .targets file uses the
'Returns' attribute, MSBuild no longer treats the 'Outputs' attribute of that file's
other targets as their return value - those targets return an empty set to <MSBuild>
callers. GetAppExtensionBundlePath and BuildAndGetAppExtensionBundlePath relied on
'Outputs="@(_AppExtensionBundlePath)"' being returned (they are invoked cross-project
by _ResolveAppExtensionReferences / _ResolveWatchAppReferences, which capture
TargetOutputs into _ResolvedAppExtensionReferences). After GetApplicationArtifacts
introduced the first 'Returns' in the file, those two targets returned nothing, so
_ResolvedAppExtensionReferences was empty and _CopyAppExtensionsToBundle copied
nothing.

Fix by giving both targets an explicit Returns equal to their Outputs, restoring
their previous return value, and add a comment documenting the pitfall. The
@(ApplicationArtifact) feature (GetApplicationArtifacts / Publish) is unchanged.

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

Copy link
Copy Markdown
Collaborator

✅ [PR Build #ee63ecc] Build passed (Detect API changes) ✅

Pipeline on Agent
Hash: ee63ecc6c82cb94664b9bc50941c9da0d382c84a [PR build]

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [PR Build #ee63ecc] Build passed (Build packages) ✅

Pipeline on Agent
Hash: ee63ecc6c82cb94664b9bc50941c9da0d382c84a [PR build]

@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: ee63ecc6c82cb94664b9bc50941c9da0d382c84a [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [PR Build #ee63ecc] Build passed (Build macOS tests) ✅

Pipeline on Agent
Hash: ee63ecc6c82cb94664b9bc50941c9da0d382c84a [PR build]

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🚀 [CI Build #ee63ecc] Test results 🚀

Test results

✅ All tests passed on VSTS: test results.

🎉 All 220 tests passed 🎉

Tests counts

✅ 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 6 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 21 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 24 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 24 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 21 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. [attempt 4] 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 Ventura (13): 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: ee63ecc6c82cb94664b9bc50941c9da0d382c84a [PR build]

@dalexsoto dalexsoto requested a review from rolfbjarne June 22, 2026 13:10
Comment on lines +98 to +125
var result = DotNet.AssertBuild (project_path, properties, timeout: TimeSpan.FromMinutes (15));

// The .ipa is built on the paired Mac and copied back to this Windows machine, so it must be
// surfaced in @(ApplicationArtifact) with the local (Windows) path. The .app bundle stays on the
// Mac, so it's intentionally not surfaced as an artifact on Windows.
var ipaPath = Path.Combine (appPath, "..", $"{project}.ipa");
Assert.That (ipaPath, Does.Exist, "ipa creation");
AssertApplicationArtifact (result.BinLogPath, ipaPath, platform, "ipa", isDirectory: false);
}

[Test]
[Category ("RemoteWindows")]
[TestCase (ApplePlatform.iOS, "ios-arm64")]
public void ArchiveOnRemoteWindowsTest (ApplePlatform platform, string runtimeIdentifiers)
{
var project = "MySimpleApp";
var configuration = "Release";
Configuration.IgnoreIfIgnoredPlatform (platform);
Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
Configuration.IgnoreIfNotOnWindows ();

var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration);
Clean (project_path);
var properties = GetDefaultProperties (runtimeIdentifiers);
properties ["ArchiveOnBuild"] = "true";
properties ["Configuration"] = configuration;

var result = DotNet.AssertBuild (project_path, properties, timeout: TimeSpan.FromMinutes (20));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

NDB, and this could be fixed in a follow-up PR if desired: building remotely is slow, so would it be possible to merge these two tests into one so that we only build one test app remotely? Setting both ArchiveOnBuild + BuildIpa seems like it could accomplish that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot make open issue so we do not forget please

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.

Created #25769 to track merging the two remote Windows tests into a single build.

@dalexsoto dalexsoto merged commit 34d480d into main Jun 22, 2026
57 checks passed
@dalexsoto dalexsoto deleted the dev/redth/apple-artifacts-plan branch June 22, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants