Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7463e2e
Expose MaciOS artifact outputs
Redth Jun 16, 2026
672e66c
Rename Apple artifact output items
Redth Jun 16, 2026
4cb54a5
Split Apple package signing metadata
Redth Jun 16, 2026
8298e84
Remove Apple package signing metadata
Redth Jun 16, 2026
d5e41ec
Remove Apple app bundle path metadata
Redth Jun 16, 2026
b0e26ef
Test Apple package output query target
Redth Jun 16, 2026
fc92ec2
Rename application artifact item group
Redth Jun 16, 2026
4bd1e60
Make application artifact target extensible
Redth Jun 17, 2026
2d01224
Strengthen application artifact extension test
Redth Jun 17, 2026
c92439d
Run artifact extensions during publish
Redth Jun 17, 2026
778f75a
Merge branch 'main' into redth/apple-artifacts-plan
dalexsoto Jun 17, 2026
390b162
Clarify artifact metadata extension guidance
Redth Jun 17, 2026
576858c
Harden application artifact extension ordering
Redth Jun 17, 2026
8beec2d
Use dotnet build artifact query examples
Redth Jun 17, 2026
10ad74d
Merge main into apple artifacts plan
Redth Jun 17, 2026
7b37b7e
Harden application artifact target graph
Redth Jun 17, 2026
57a0455
Guard application artifact producer conditions
Redth Jun 17, 2026
5c1a952
Simplify application artifact query target
Redth Jun 17, 2026
73624bf
Merge branch 'main' into dev/redth/apple-artifacts-plan
dalexsoto Jun 18, 2026
c53752e
Merge branch 'main' into dev/redth/apple-artifacts-plan
dalexsoto Jun 18, 2026
33fedd1
[tests] Address review feedback for application artifact tests
dalexsoto Jun 18, 2026
eebb829
[docs] Clarify GetApplicationArtifacts contract and query examples
dalexsoto Jun 18, 2026
1c8dc7d
[msbuild] Make @(ApplicationArtifact) paths absolute and fix artifact…
dalexsoto Jun 18, 2026
e0d618e
[msbuild] Collect the Windows Pair-to-Mac .xcarchive after copy-back
dalexsoto Jun 19, 2026
c540b7e
[tests] Add Windows Pair-to-Mac @(ApplicationArtifact) tests
dalexsoto Jun 19, 2026
9aabadf
[msbuild] Don't collect the .app artifact on remote Windows builds
dalexsoto Jun 19, 2026
129743c
Merge branch 'main' into dev/redth/apple-artifacts-plan
dalexsoto Jun 19, 2026
ee63ecc
[msbuild] Fix app extensions not being embedded after adding @(Applic…
dalexsoto Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/building-apps/build-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,35 @@ An item group that contains environment variables that will be set when the app
> [!NOTE]
> This only applies when launching the app from the command line (`dotnet run` or `dotnet build -t:Run`), not when launching from the IDE.

## ApplicationArtifact

An item group that contains final application artifacts produced by Apple platform builds and publishes. The item identity is the absolute path to the artifact. This can include:

* `.app` app bundles for iOS, tvOS, macOS, and Mac Catalyst apps.
* `.ipa` packages when [BuildIpa](build-properties.md#buildipa) is enabled.
* `.pkg` installer packages when [CreatePackage](build-properties.md#createpackage) is enabled.
* `.xcarchive` directories when [ArchiveOnBuild](build-properties.md#archiveonbuild) is enabled.

The following metadata is set:

* `PackageFormat`: The artifact format. Possible values are `app`, `ipa`, `pkg`, and `xcarchive`.
* `IsDirectory`: `true` for `.app` and `.xcarchive` outputs; `false` for `.ipa` and `.pkg` outputs.
* `PlatformName`: The Apple platform name, such as `iOS`, `tvOS`, `macOS`, or `MacCatalyst`.
* `BundleIdentifier`: The resolved app bundle identifier.

Example:

```xml
<Target Name="WriteApplicationArtifacts" AfterTargets="Build">
<WriteLinesToFile
File="$(OutputPath)application-artifacts.txt"
Lines="%(ApplicationArtifact.Identity)|%(ApplicationArtifact.PackageFormat)"
Overwrite="true" />
</Target>
```

See also the [GetApplicationArtifacts](build-targets.md#getapplicationartifacts) target.

## NativeReference

An item group that contains any native references that should be linked into
Expand Down
40 changes: 40 additions & 0 deletions docs/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ Only applicable to iOS projects (since only iOS projects can be built remotely f

If an Xcode archive should be created at the end of the build.

Created archives are exposed in the [ApplicationArtifact](build-items.md#applicationartifact) item group.

## BGenEmitDebugInformation

Whether the `bgen` tool (the binding generator) should emit debug information or not.
Expand Down Expand Up @@ -139,6 +141,8 @@ Only applicable to iOS and tvOS projects.

See [CreatePackage](#createpackage) for macOS and Mac Catalyst projects.

Created IPA packages are exposed in the [ApplicationArtifact](build-items.md#applicationartifact) item group.

## BundleCreateDump

CoreCLR has a command-line utility called [`createdump`][createdump] to create
Expand Down Expand Up @@ -350,6 +354,8 @@ Only applicable to macOS and Mac Catalyst projects.

See [BuildIpa](#buildipa) for iOS and tvOS projects.

Created PKG packages are exposed in the [ApplicationArtifact](build-items.md#applicationartifact) item group.

## Device

Specifies which mobile device or simulator to target when using `dotnet run --device <Device>` or MSBuild targets that interact with devices (such as `Run`, `Install`, or `Uninstall`).
Expand Down Expand Up @@ -535,6 +541,36 @@ Default: true

Where the generated source from the generator are saved.

## GetApplicationArtifactsDependsOn

A semi-colon delimited property that can be used to extend the
[GetApplicationArtifacts](build-targets.md#getapplicationartifacts) and
`Publish` targets. `Build` is a mandatory dependency of
`GetApplicationArtifacts`; MSBuild targets added to this property execute after
the platform build has collected `@(ApplicationArtifact)` items and before
`GetApplicationArtifacts` or `Publish` returns them.

This can be used by SDKs such as .NET MAUI to add shared application metadata
to platform-produced artifacts. Extension targets should update existing
`@(ApplicationArtifact)` items to add metadata; they should only add new items
when introducing additional artifacts.

Example:

```xml
<PropertyGroup>
<GetApplicationArtifactsDependsOn>$(GetApplicationArtifactsDependsOn);AddApplicationArtifactMetadata</GetApplicationArtifactsDependsOn>
</PropertyGroup>

<Target Name="AddApplicationArtifactMetadata">
<ItemGroup>
<ApplicationArtifact Update="@(ApplicationArtifact)">
<ApplicationTitle>$(ApplicationTitle)</ApplicationTitle>
</ApplicationArtifact>
</ItemGroup>
</Target>
```

## IBToolPath

The full path to the `ibtool` tool.
Expand Down Expand Up @@ -669,6 +705,8 @@ Specifies the path to the resulting .ipa file when creating an IPA package (see

Only applicable to iOS and tvOS projects.

The resulting IPA is exposed in the [ApplicationArtifact](build-items.md#applicationartifact) item group.

## IsAppExtension

If a project is an app extension.
Expand Down Expand Up @@ -1139,6 +1177,8 @@ Specifies the path to the resulting .pkg file when creating a package (see [Crea

Only applicable to macOS and Mac Catalyst apps.

The resulting PKG is exposed in the [ApplicationArtifact](build-items.md#applicationartifact) item group.

## PlutilPath

The full path to the `plutil` command-line tool.
Expand Down
30 changes: 30 additions & 0 deletions docs/building-apps/build-targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,36 @@ $ dotnet run --device UDID

Added in .NET 11.

## GetApplicationArtifacts

Builds the project and returns the `@(ApplicationArtifact)` item group. This
target always runs `Build` first so platform `.app`, `.ipa`, `.pkg`, and
`.xcarchive` artifacts are produced and collected before any custom metadata
extension targets run. The `Publish` target returns the same item group for
artifacts it creates.

The returned item group reflects the artifacts the current invocation is
configured to build; it does not scan for `.ipa`, `.pkg`, or `.xcarchive` files
left on disk by a previous build. The `.app` bundle is always collected for an
application project (excluding app extension and watch app projects), while the
`.ipa`, `.pkg`, and `.xcarchive` outputs are only collected when
[BuildIpa](build-properties.md#buildipa),
[CreatePackage](build-properties.md#createpackage), or
[ArchiveOnBuild](build-properties.md#archiveonbuild) (respectively) is enabled in
the same invocation. The `Publish` target enables `BuildIpa` (mobile) or
`CreatePackage` (desktop) by default.

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

See [ApplicationArtifact](build-items.md#applicationartifact) for supported metadata.

Targets that need to add or update `@(ApplicationArtifact)` metadata before
`GetApplicationArtifacts` or `Publish` returns can append to
[GetApplicationArtifactsDependsOn](build-properties.md#getapplicationartifactsdependson).

## Run

Builds the source code within a project and all dependencies, and then deploys and runs it
Expand Down
2 changes: 1 addition & 1 deletion dotnet/targets/Xamarin.Shared.Sdk.Publish.targets
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
Condition="$(RuntimeIdentifiers.Contains('iossimulator-')) Or $(RuntimeIdentifiers.Contains('tvossimulator-'))"
/>
</Target>
<Target Name="Publish" DependsOnTargets="_PrePublish;Build">
<Target Name="Publish" DependsOnTargets="_PrePublish;GetApplicationArtifacts" Returns="@(ApplicationArtifact)">
<Message Importance="high" Text="Created the package: $(IpaPackagePath)" Condition="'$(BuildIpa)' == 'true' And '$(SdkIsMobile)' == 'true'" />
<Message Importance="high" Text="Created the package: $(PkgPackagePath)" Condition="'$(CreatePackage)' == 'true' And '$(SdkIsDesktop)' == 'true'" />
</Target>
Expand Down
65 changes: 63 additions & 2 deletions msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2370,6 +2370,24 @@ Copyright (C) 2018 Microsoft. All rights reserved.

<Target Name="Codesign" Condition="'$(_CanOutputAppBundle)' == 'true'" DependsOnTargets="$(CodesignDependsOn)" />

<!-- On a Windows (Pair to Mac) build the .app bundle is produced on the Mac and is not copied
back to the local $(AppBundleDir) (CopyAppBundleFromMac, and only when CopyAppBundle is set,
extracts it to a separate AppBundle\ subfolder instead). Skip collecting it on remote builds
($(IsRemoteBuild) == 'true') so we never surface an incomplete local bundle; surfacing the
.app on Windows is left for a follow-up. -->
<Target Name="_CollectAppleAppBundleOutput"
Condition="'$(_CanOutputAppBundle)' == 'true' And '$(OutputType)' == 'Exe' And '$(IsAppExtension)' != 'true' And '$(IsWatchApp)' != 'true' And '$(IsRemoteBuild)' != 'true' And Exists('$(AppBundleDir)')"
AfterTargets="Codesign">
<ItemGroup>
<ApplicationArtifact Include="$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(AppBundleDir)'))">
Comment thread
rolfbjarne marked this conversation as resolved.
<PackageFormat>app</PackageFormat>
<IsDirectory>true</IsDirectory>
<PlatformName>$(_PlatformName)</PlatformName>
<BundleIdentifier>$(_BundleIdentifier)</BundleIdentifier>
</ApplicationArtifact>
</ItemGroup>
</Target>

<!--

Code signing
Expand Down Expand Up @@ -2690,9 +2708,14 @@ Copyright (C) 2018 Microsoft. All rights reserved.

<!-- App extensions -->

<Target Name="GetAppExtensionBundlePath" DependsOnTargets="_GenerateBundleName;$(GetTargetPathDependsOn)" Outputs="@(_AppExtensionBundlePath)" />
<!-- These two targets are invoked across projects (via the MSBuild task) to get the app extension's
bundle path back as TargetOutputs. They must specify 'Returns' explicitly: once any target in this
file uses the 'Returns' attribute (e.g. GetApplicationArtifacts), MSBuild stops treating the
'Outputs' attribute of targets without 'Returns' as their return value, so relying on 'Outputs'
alone would return an empty set and the extension would not be embedded into the container app. -->
<Target Name="GetAppExtensionBundlePath" DependsOnTargets="_GenerateBundleName;$(GetTargetPathDependsOn)" Outputs="@(_AppExtensionBundlePath)" Returns="@(_AppExtensionBundlePath)" />

<Target Name="BuildAndGetAppExtensionBundlePath" DependsOnTargets="Build" Outputs="@(_AppExtensionBundlePath)" />
<Target Name="BuildAndGetAppExtensionBundlePath" DependsOnTargets="Build" Outputs="@(_AppExtensionBundlePath)" Returns="@(_AppExtensionBundlePath)" />

<Target Name="_AssignAppExtensionConfiguration" Condition="'@(_AppExtensionReference)' != ''">
<!-- assign configs if building a solution file -->
Expand Down Expand Up @@ -3257,6 +3280,28 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</Archive>
</Target>

<Target Name="_CollectAppleArchiveOutput"
Condition="'$(ArchiveOnBuild)' == 'true'"
AfterTargets="Archive">
<!-- On a Windows (Pair to Mac) build the archive is copied back to Windows by
CopyArchiveFromMac (the last target in ArchiveDependsOn), which sets $(ArchivePath)
to the local Windows path while $(ArchiveDir) keeps pointing to the Mac path. Prefer
$(ArchiveDir) when it exists locally (the normal macOS build) and fall back to the
copied-back $(ArchivePath) otherwise. -->
<PropertyGroup>
<_ApplicationArtifactArchivePath>$(ArchiveDir)</_ApplicationArtifactArchivePath>
<_ApplicationArtifactArchivePath Condition="'$(ArchivePath)' != '' And !Exists('$(_ApplicationArtifactArchivePath)')">$(ArchivePath)</_ApplicationArtifactArchivePath>
</PropertyGroup>
<ItemGroup Condition="'$(_ApplicationArtifactArchivePath)' != '' And Exists('$(_ApplicationArtifactArchivePath)')">
<ApplicationArtifact Include="$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(_ApplicationArtifactArchivePath)'))">
<PackageFormat>xcarchive</PackageFormat>
<IsDirectory>true</IsDirectory>
<PlatformName>$(_PlatformName)</PlatformName>
<BundleIdentifier>$(_BundleIdentifier)</BundleIdentifier>
</ApplicationArtifact>
</ItemGroup>
</Target>

<Target Name="_GenerateBundleName" Condition="'$(_CanOutputAppBundle)' == 'true'" DependsOnTargets="_ComputeTargetArchitectures;_ParseBundlerArguments">
<PropertyGroup Condition="'$(AppBundleDir)' == ''">
<_AppContainerDir Condition="'$(IsAppDistribution)' != 'true'">$(DeviceSpecificOutputPath)</_AppContainerDir>
Expand Down Expand Up @@ -3360,6 +3405,19 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</CreateInstallerPackage>
</Target>

<Target Name="_CollectApplePkgOutput"
Condition="'$(CreatePackage)' == 'true' And '$(PkgPackagePath)' != '' And Exists('$(PkgPackagePath)')"
AfterTargets="_CreateInstaller">
<ItemGroup>
<ApplicationArtifact Include="$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(PkgPackagePath)'))">
<PackageFormat>pkg</PackageFormat>
<IsDirectory>false</IsDirectory>
<PlatformName>$(_PlatformName)</PlatformName>
<BundleIdentifier>$(_BundleIdentifier)</BundleIdentifier>
</ApplicationArtifact>
</ItemGroup>
</Target>

<!--
Creates a plist with the entitlements used to sign the App bundle.
MacCatalyst apps don't have a compiled entitlements file in the app bundle,
Expand Down Expand Up @@ -3390,12 +3448,15 @@ Copyright (C) 2018 Microsoft. All rights reserved.
_PackageOnDemandResources;
_ZipIpa
</CreateIpaDependsOn>

</PropertyGroup>

<Target Name="_BeforeCreateIpaForDistribution" Condition="'$(IsAppDistribution)' == 'true'" DependsOnTargets="$(_BeforeCreateIpaForDistributionDependsOn)" />

<Target Name="CreateIpa" Condition="'$(_CanArchive)' == 'true' And '$(SdkIsDesktop)' != 'true'" DependsOnTargets="$(CreateIpaDependsOn)" />

<Target Name="GetApplicationArtifacts" DependsOnTargets="Build;$(GetApplicationArtifactsDependsOn)" Returns="@(ApplicationArtifact)" />

<PropertyGroup>
<PrepareAssemblies Condition="'$(PrepareAssemblies)' == ''">false</PrepareAssemblies>
</PropertyGroup>
Expand Down
13 changes: 13 additions & 0 deletions msbuild/Xamarin.Shared/Xamarin.iOS.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,19 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved.
</Zip>
</Target>

<Target Name="_CollectAppleIpaOutput"
Condition="'$(BuildIpa)' == 'true' And '$(IpaPackagePath)' != '' And Exists('$(IpaPackagePath)')"
AfterTargets="CreateIpa">
<ItemGroup>
<ApplicationArtifact Include="$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(IpaPackagePath)'))">
<PackageFormat>ipa</PackageFormat>
<IsDirectory>false</IsDirectory>
<PlatformName>$(_PlatformName)</PlatformName>
<BundleIdentifier>$(_BundleIdentifier)</BundleIdentifier>
</ApplicationArtifact>
</ItemGroup>
</Target>

<Import Project="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).After.targets"
Condition="Exists('$(MSBuildThisFileDirectory)$(MSBuildThisFileName).After.targets')"/>

Expand Down
17 changes: 17 additions & 0 deletions tests/dotnet/MySimpleAppWithArtifactMetadata/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;

using Foundation;

namespace MySimpleAppWithArtifactMetadata {
public class Program {
static int Main (string [] args)
{
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly

Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD"));

return args.Length;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
3 changes: 3 additions & 0 deletions tests/dotnet/MySimpleAppWithArtifactMetadata/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TOP=../../..

include $(TOP)/tests/common/shared-dotnet-test.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-ios</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-macos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
32 changes: 32 additions & 0 deletions tests/dotnet/MySimpleAppWithArtifactMetadata/shared.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<OutputType>Exe</OutputType>

<ApplicationTitle>MySimpleAppWithArtifactMetadata</ApplicationTitle>
<ApplicationId>com.xamarin.mysimpleappwithartifactmetadata</ApplicationId>
<ApplicationVersion>3.14</ApplicationVersion>

<!-- Simulates an SDK (such as .NET MAUI) enriching the platform-produced @(ApplicationArtifact) items -->
<GetApplicationArtifactsDependsOn>$(GetApplicationArtifactsDependsOn);AddMauiApplicationArtifactMetadata</GetApplicationArtifactsDependsOn>
</PropertyGroup>

<Import Project="../../common/shared-dotnet.csproj" />

<ItemGroup>
<Compile Include="../*.cs" />
</ItemGroup>

<Target Name="AddMauiApplicationArtifactMetadata">
<ItemGroup>
<_MauiObservedAppArtifact Include="@(ApplicationArtifact)" Condition="'%(ApplicationArtifact.PackageFormat)' == 'app'" />
<_MauiObservedPackageArtifact Include="@(ApplicationArtifact)" Condition="'%(ApplicationArtifact.PackageFormat)' == '$(ExpectedAugmentedPackageFormat)'" />
<ApplicationArtifact Update="@(ApplicationArtifact)">
<ApplicationTitle>My MAUI App</ApplicationTitle>
<MauiObservedPackageFormat>%(ApplicationArtifact.PackageFormat)</MauiObservedPackageFormat>
</ApplicationArtifact>
</ItemGroup>
<Error Condition="'@(_MauiObservedAppArtifact)' == ''" Text="Expected app ApplicationArtifact items before MAUI metadata is added." />
<Error Condition="'@(_MauiObservedPackageArtifact)' == ''" Text="Expected $(ExpectedAugmentedPackageFormat) ApplicationArtifact items before MAUI metadata is added." />
</Target>
</Project>
3 changes: 3 additions & 0 deletions tests/dotnet/MySimpleAppWithArtifactMetadata/shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TOP=../../../..
TESTNAME=MySimpleAppWithArtifactMetadata
include $(TOP)/tests/common/shared-dotnet.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-tvos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
Loading
Loading