diff --git a/docs/building-apps/build-items.md b/docs/building-apps/build-items.md
index 2fd81202cf18..84329b453a04 100644
--- a/docs/building-apps/build-items.md
+++ b/docs/building-apps/build-items.md
@@ -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
+
+
+
+```
+
+See also the [GetApplicationArtifacts](build-targets.md#getapplicationartifacts) target.
+
## NativeReference
An item group that contains any native references that should be linked into
diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md
index 634396282b90..3f61ec1902fa 100644
--- a/docs/building-apps/build-properties.md
+++ b/docs/building-apps/build-properties.md
@@ -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.
@@ -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
@@ -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 ` or MSBuild targets that interact with devices (such as `Run`, `Install`, or `Uninstall`).
@@ -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
+
+ $(GetApplicationArtifactsDependsOn);AddApplicationArtifactMetadata
+
+
+
+
+
+ $(ApplicationTitle)
+
+
+
+```
+
## IBToolPath
The full path to the `ibtool` tool.
@@ -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.
@@ -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.
diff --git a/docs/building-apps/build-targets.md b/docs/building-apps/build-targets.md
index fbf0abff811e..82ebe9677f5d 100644
--- a/docs/building-apps/build-targets.md
+++ b/docs/building-apps/build-targets.md
@@ -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
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.Publish.targets b/dotnet/targets/Xamarin.Shared.Sdk.Publish.targets
index c053793baf15..8f6dca62905e 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.Publish.targets
+++ b/dotnet/targets/Xamarin.Shared.Sdk.Publish.targets
@@ -20,7 +20,7 @@
Condition="$(RuntimeIdentifiers.Contains('iossimulator-')) Or $(RuntimeIdentifiers.Contains('tvossimulator-'))"
/>
-
+
diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets
index 38577d9196a7..5accac63eb06 100644
--- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets
+++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets
@@ -2370,6 +2370,24 @@ Copyright (C) 2018 Microsoft. All rights reserved.
+
+
+
+
+ app
+ true
+ $(_PlatformName)
+ $(_BundleIdentifier)
+
+
+
+
-
+
+
-
+
@@ -3257,6 +3280,28 @@ Copyright (C) 2018 Microsoft. All rights reserved.
+
+
+
+ <_ApplicationArtifactArchivePath>$(ArchiveDir)
+ <_ApplicationArtifactArchivePath Condition="'$(ArchivePath)' != '' And !Exists('$(_ApplicationArtifactArchivePath)')">$(ArchivePath)
+
+
+
+ xcarchive
+ true
+ $(_PlatformName)
+ $(_BundleIdentifier)
+
+
+
+
<_AppContainerDir Condition="'$(IsAppDistribution)' != 'true'">$(DeviceSpecificOutputPath)
@@ -3360,6 +3405,19 @@ Copyright (C) 2018 Microsoft. All rights reserved.
+
+
+
+ pkg
+ false
+ $(_PlatformName)
+ $(_BundleIdentifier)
+
+
+
+
+ $(GetApplicationArtifactsDependsOn);AddMauiApplicationArtifactMetadata
+
+
+
+
+
+
+
+
+
+
+ <_MauiObservedAppArtifact Include="@(ApplicationArtifact)" Condition="'%(ApplicationArtifact.PackageFormat)' == 'app'" />
+ <_MauiObservedPackageArtifact Include="@(ApplicationArtifact)" Condition="'%(ApplicationArtifact.PackageFormat)' == '$(ExpectedAugmentedPackageFormat)'" />
+
+ My MAUI App
+ %(ApplicationArtifact.PackageFormat)
+
+
+
+
+
+
diff --git a/tests/dotnet/MySimpleAppWithArtifactMetadata/shared.mk b/tests/dotnet/MySimpleAppWithArtifactMetadata/shared.mk
new file mode 100644
index 000000000000..0e8169558074
--- /dev/null
+++ b/tests/dotnet/MySimpleAppWithArtifactMetadata/shared.mk
@@ -0,0 +1,3 @@
+TOP=../../../..
+TESTNAME=MySimpleAppWithArtifactMetadata
+include $(TOP)/tests/common/shared-dotnet.mk
diff --git a/tests/dotnet/MySimpleAppWithArtifactMetadata/tvOS/Makefile b/tests/dotnet/MySimpleAppWithArtifactMetadata/tvOS/Makefile
new file mode 100644
index 000000000000..110d078f4577
--- /dev/null
+++ b/tests/dotnet/MySimpleAppWithArtifactMetadata/tvOS/Makefile
@@ -0,0 +1 @@
+include ../shared.mk
diff --git a/tests/dotnet/MySimpleAppWithArtifactMetadata/tvOS/MySimpleAppWithArtifactMetadata.csproj b/tests/dotnet/MySimpleAppWithArtifactMetadata/tvOS/MySimpleAppWithArtifactMetadata.csproj
new file mode 100644
index 000000000000..bd487ddcd88d
--- /dev/null
+++ b/tests/dotnet/MySimpleAppWithArtifactMetadata/tvOS/MySimpleAppWithArtifactMetadata.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-tvos
+
+
+
diff --git a/tests/dotnet/UnitTests/PostBuildTest.cs b/tests/dotnet/UnitTests/PostBuildTest.cs
index 3f8d8b76e8a6..cf2a01541974 100644
--- a/tests/dotnet/UnitTests/PostBuildTest.cs
+++ b/tests/dotnet/UnitTests/PostBuildTest.cs
@@ -1,3 +1,5 @@
+using System.Text.Json;
+
using Microsoft.Build.Framework;
using Microsoft.Build.Logging.StructuredLogger;
using Mono.Cecil;
@@ -45,6 +47,7 @@ public void ArchiveTest (ApplePlatform platform, string runtimeIdentifiers)
Assert.That (archiveDirs.Count, Is.GreaterThan (0), "ArchiveDir");
var archiveDir = archiveDirs [0]!.Trim ();
Assert.That (archiveDir, Does.Exist, "Archive directory existence");
+ AssertApplicationArtifact (result.BinLogPath, archiveDir, platform, "xcarchive", isDirectory: true);
AssertDSymDirectory (appPath);
}
@@ -64,15 +67,141 @@ public void BuildIpaTest (ApplePlatform platform, string runtimeIdentifiers)
properties ["BuildIpa"] = "true";
properties ["Configuration"] = configuration;
- DotNet.AssertBuild (project_path, properties);
+ var result = DotNet.AssertBuild (project_path, properties);
var pkgPath = Path.Combine (appPath, "..", $"{project}.ipa");
Assert.That (pkgPath, Does.Exist, "pkg creation");
+ AssertApplicationArtifact (result.BinLogPath, appPath, platform, "app", isDirectory: true);
+ AssertApplicationArtifact (result.BinLogPath, pkgPath, platform, "ipa", isDirectory: false);
AssertBundleAssembliesStripStatus (appPath, true);
AssertDSymDirectory (appPath);
}
+ [Test]
+ [Category ("RemoteWindows")]
+ [TestCase (ApplePlatform.iOS, "ios-arm64")]
+ public void BuildIpaOnRemoteWindowsTest (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 ["BuildIpa"] = "true";
+ properties ["Configuration"] = configuration;
+
+ 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));
+
+ // The .xcarchive is produced on the paired Mac and copied back to this Windows machine by
+ // CopyArchiveFromMac, which sets $(ArchivePath) to the local (Windows) path while $(ArchiveDir)
+ // keeps pointing at the Mac path. The Windows archive path is date-based, so match on the
+ // package format rather than an exact path.
+ AssertApplicationArtifact (result.BinLogPath, platform, "xcarchive", isDirectory: true);
+ }
+
+ [Test]
+ [TestCase (ApplePlatform.iOS, "ios-arm64")]
+ [TestCase (ApplePlatform.TVOS, "tvos-arm64")]
+ public void GetApplicationArtifactsIpaTest (ApplePlatform platform, string runtimeIdentifiers)
+ {
+ var project = "MySimpleApp";
+ var configuration = "Release";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+
+ var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration);
+ Clean (project_path);
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties ["BuildIpa"] = "true";
+ properties ["Configuration"] = configuration;
+
+ var outputs = GetApplicationArtifacts (project_path, properties);
+ var pkgPath = Path.Combine (appPath, "..", $"{project}.ipa");
+
+ Assert.That (pkgPath, Does.Exist, "pkg creation");
+ AssertApplicationArtifact (outputs, appPath, platform, "app", isDirectory: true);
+ AssertApplicationArtifact (outputs, pkgPath, platform, "ipa", isDirectory: false);
+ }
+
+ [Test]
+ [TestCase (ApplePlatform.iOS, "ios-arm64", "ipa")]
+ [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64", "pkg")]
+ public void GetApplicationArtifactsDependsOnTest (ApplePlatform platform, string runtimeIdentifiers, string packageFormat)
+ {
+ var project = "MySimpleAppWithArtifactMetadata";
+ var configuration = "Release";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+
+ var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration);
+ Clean (project_path);
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties ["BuildIpa"] = packageFormat == "ipa" ? "true" : "false";
+ properties ["CreatePackage"] = packageFormat == "pkg" ? "true" : "false";
+ properties ["Configuration"] = configuration;
+ properties ["ExpectedAugmentedPackageFormat"] = packageFormat;
+
+ var outputs = GetApplicationArtifacts (project_path, properties);
+ var appOutput = AssertApplicationArtifact (outputs, appPath, platform, "app", isDirectory: true);
+ var packageOutput = AssertApplicationArtifact (outputs, platform, packageFormat, isDirectory: false);
+ Assert.That (GetMetadata (appOutput, "ApplicationTitle"), Is.EqualTo ("My MAUI App"), "ApplicationTitle");
+ Assert.That (GetMetadata (appOutput, "MauiObservedPackageFormat"), Is.EqualTo ("app"), "MauiObservedPackageFormat");
+ Assert.That (GetMetadata (packageOutput, "ApplicationTitle"), Is.EqualTo ("My MAUI App"), "ApplicationTitle");
+ Assert.That (GetMetadata (packageOutput, "MauiObservedPackageFormat"), Is.EqualTo (packageFormat), "MauiObservedPackageFormat");
+ }
+
+ [Test]
+ [TestCase (ApplePlatform.iOS, "ios-arm64")]
+ [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64")]
+ public void GetApplicationArtifactsArchiveTest (ApplePlatform platform, string runtimeIdentifiers)
+ {
+ var project = "MySimpleApp";
+ var configuration = "Release";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+
+ var project_path = GetProjectPath (project, runtimeIdentifiers, platform, out _, configuration: configuration);
+ Clean (project_path);
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties ["ArchiveOnBuild"] = "true";
+ properties ["Configuration"] = configuration;
+
+ var outputs = GetApplicationArtifacts (project_path, properties);
+ AssertApplicationArtifact (outputs, platform, "xcarchive", isDirectory: true);
+ }
+
[Test]
[TestCase ("MySimpleApp", ApplePlatform.iOS, "ios-arm64", true)]
[TestCase ("MySimpleApp", ApplePlatform.iOS, "ios-arm64", false)]
@@ -137,10 +266,35 @@ public void BuildPackageTest (string project, ApplePlatform platform, string run
var properties = GetDefaultProperties (runtimeIdentifiers);
properties ["CreatePackage"] = "true";
- DotNet.AssertBuild (project_path, properties);
+ var result = DotNet.AssertBuild (project_path, properties);
+
+ var pkgPath = Path.Combine (appPath, "..", $"{project}-{projectVersion}.pkg");
+ Assert.That (pkgPath, Does.Exist, "pkg creation");
+ AssertApplicationArtifact (result.BinLogPath, appPath, platform, "app", isDirectory: true);
+ AssertApplicationArtifact (result.BinLogPath, pkgPath, platform, "pkg", isDirectory: false);
+ }
+
+ [Test]
+ [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64")]
+ [TestCase (ApplePlatform.MacOSX, "osx-x64")]
+ public void GetApplicationArtifactsPkgTest (ApplePlatform platform, string runtimeIdentifiers)
+ {
+ var project = "MySimpleApp";
+ var projectVersion = "3.14";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+ var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
+ Clean (project_path);
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties ["CreatePackage"] = "true";
+
+ var outputs = GetApplicationArtifacts (project_path, properties);
var pkgPath = Path.Combine (appPath, "..", $"{project}-{projectVersion}.pkg");
+
Assert.That (pkgPath, Does.Exist, "pkg creation");
+ AssertApplicationArtifact (outputs, appPath, platform, "app", isDirectory: true);
+ AssertApplicationArtifact (outputs, pkgPath, platform, "pkg", isDirectory: false);
}
[TestCase (ApplePlatform.iOS, "ios-arm64")]
@@ -180,9 +334,39 @@ public void PublishTest (ApplePlatform platform, string runtimeIdentifiers)
var properties = GetDefaultProperties (runtimeIdentifiers);
properties [pathVariable] = pkgPath;
- DotNet.AssertPublish (project_path, properties);
+ var result = DotNet.AssertPublish (project_path, properties);
Assert.That (pkgPath, Does.Exist, "ipa/pkg creation");
+ AssertApplicationArtifact (result.BinLogPath, pkgPath, platform, packageExtension, isDirectory: false);
+ }
+
+ [TestCase (ApplePlatform.iOS, "ios-arm64", "ipa", "IpaPackagePath")]
+ [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64", "pkg", "PkgPackagePath")]
+ public void PublishApplicationArtifactsDependsOnTest (ApplePlatform platform, string runtimeIdentifiers, string packageFormat, string pathVariable)
+ {
+ var project = "MySimpleAppWithArtifactMetadata";
+ var configuration = "Release";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+
+ var project_path = GetProjectPath (project, runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration);
+ Clean (project_path);
+
+ var tmpdir = Cache.CreateTemporaryDirectory ();
+ var pkgPath = Path.Combine (tmpdir, $"MyPackage.{packageFormat}");
+
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties [pathVariable] = pkgPath;
+ properties ["Configuration"] = configuration;
+ properties ["ExpectedAugmentedPackageFormat"] = packageFormat;
+
+ var outputs = GetApplicationArtifacts (project_path, properties, target: "Publish");
+ var appOutput = AssertApplicationArtifact (outputs, appPath, platform, "app", isDirectory: true);
+ var packageOutput = AssertApplicationArtifact (outputs, pkgPath, platform, packageFormat, isDirectory: false);
+ Assert.That (GetMetadata (appOutput, "ApplicationTitle"), Is.EqualTo ("My MAUI App"), "ApplicationTitle");
+ Assert.That (GetMetadata (appOutput, "MauiObservedPackageFormat"), Is.EqualTo ("app"), "MauiObservedPackageFormat");
+ Assert.That (GetMetadata (packageOutput, "ApplicationTitle"), Is.EqualTo ("My MAUI App"), "ApplicationTitle");
+ Assert.That (GetMetadata (packageOutput, "MauiObservedPackageFormat"), Is.EqualTo (packageFormat), "MauiObservedPackageFormat");
}
@@ -374,7 +558,81 @@ public void StaticFrameworksNotInPostProcessing (ApplePlatform platform, string
Assert.That (staticFrameworkItems, Is.Empty, $"Static framework XStaticArTest should not be in post-processing items. All items:\n\t{string.Join ("\n\t", postProcessingItems.Select (i => i.ItemSpec))}");
}
+ static ITaskItem AssertApplicationArtifact (string binLogPath, string path, ApplePlatform platform, string packageFormat, bool isDirectory)
+ {
+ var outputs = GetItems (binLogPath, "ApplicationArtifact");
+ var fullPath = Path.GetFullPath (path);
+ var output = outputs.SingleOrDefault (v => Path.GetFullPath (v.ItemSpec) == fullPath);
+ Assert.That (output, Is.Not.Null, $"Could not find {packageFormat} output for {fullPath}. All outputs:\n\t{string.Join ("\n\t", outputs.Select (v => v.ItemSpec))}");
+ Assert.That (output!.GetMetadata ("PackageFormat"), Is.EqualTo (packageFormat), "PackageFormat");
+ Assert.That (output.GetMetadata ("IsDirectory"), Is.EqualTo (isDirectory ? "true" : "false"), "IsDirectory");
+ Assert.That (output.GetMetadata ("PlatformName"), Is.EqualTo (platform.AsString ()), "PlatformName");
+ Assert.That (output.GetMetadata ("BundleIdentifier"), Is.Not.Empty, "BundleIdentifier");
+ Assert.That (output.GetMetadata ("ArtifactKind"), Is.Null.Or.Empty, "ArtifactKind");
+ Assert.That (output.GetMetadata ("AppBundlePath"), Is.Null.Or.Empty, "AppBundlePath");
+ Assert.That (output.GetMetadata ("CodeSigned"), Is.Null.Or.Empty, "CodeSigned");
+ Assert.That (output.GetMetadata ("Signed"), Is.Null.Or.Empty, "Signed");
+ Assert.That (output.GetMetadata ("PackageSigned"), Is.Null.Or.Empty, "PackageSigned");
+ return output;
+ }
+
+ static ITaskItem AssertApplicationArtifact (string binLogPath, ApplePlatform platform, string packageFormat, bool isDirectory)
+ {
+ var outputs = GetItems (binLogPath, "ApplicationArtifact");
+ var matchingOutputs = outputs.Where (v => v.GetMetadata ("PackageFormat") == packageFormat).ToList ();
+ Assert.That (matchingOutputs, Has.Count.EqualTo (1), $"Expected one {packageFormat} output. All outputs:\n\t{string.Join ("\n\t", outputs.Select (v => v.ItemSpec))}");
+
+ var itemSpec = matchingOutputs [0].ItemSpec;
+ Assert.That (itemSpec, Does.Exist, packageFormat);
+ return AssertApplicationArtifact (binLogPath, itemSpec, platform, packageFormat, isDirectory);
+ }
+
+ static JsonElement [] GetApplicationArtifacts (string projectPath, Dictionary properties, string target = "GetApplicationArtifacts")
+ {
+ using var document = JsonDocument.Parse (DotNet.GetItems (projectPath, "ApplicationArtifact", target: target, properties: properties));
+ var outputs = document.RootElement.GetProperty ("Items").GetProperty ("ApplicationArtifact").EnumerateArray ().Select (v => v.Clone ()).ToArray ();
+ Assert.That (outputs, Is.Not.Empty, "ApplicationArtifact items");
+ return outputs;
+ }
+
+ static JsonElement AssertApplicationArtifact (JsonElement [] outputs, string path, ApplePlatform platform, string packageFormat, bool isDirectory)
+ {
+ var fullPath = Path.GetFullPath (path);
+ var output = outputs.SingleOrDefault (v => Path.GetFullPath (GetMetadata (v, "FullPath")) == fullPath);
+ Assert.That (output.ValueKind, Is.Not.EqualTo (JsonValueKind.Undefined), $"Could not find {packageFormat} output for {fullPath}. All outputs:\n\t{string.Join ("\n\t", outputs.Select (v => GetMetadata (v, "FullPath")))}");
+ Assert.That (GetMetadata (output, "PackageFormat"), Is.EqualTo (packageFormat), "PackageFormat");
+ Assert.That (GetMetadata (output, "IsDirectory"), Is.EqualTo (isDirectory ? "true" : "false"), "IsDirectory");
+ Assert.That (GetMetadata (output, "PlatformName"), Is.EqualTo (platform.AsString ()), "PlatformName");
+ Assert.That (GetMetadata (output, "BundleIdentifier"), Is.Not.Empty, "BundleIdentifier");
+ Assert.That (GetMetadata (output, "ArtifactKind"), Is.Empty, "ArtifactKind");
+ Assert.That (GetMetadata (output, "AppBundlePath"), Is.Empty, "AppBundlePath");
+ Assert.That (GetMetadata (output, "CodeSigned"), Is.Empty, "CodeSigned");
+ Assert.That (GetMetadata (output, "Signed"), Is.Empty, "Signed");
+ Assert.That (GetMetadata (output, "PackageSigned"), Is.Empty, "PackageSigned");
+ return output;
+ }
+
+ static JsonElement AssertApplicationArtifact (JsonElement [] outputs, ApplePlatform platform, string packageFormat, bool isDirectory)
+ {
+ var matchingOutputs = outputs.Where (v => GetMetadata (v, "PackageFormat") == packageFormat).ToArray ();
+ Assert.That (matchingOutputs, Has.Length.EqualTo (1), $"Expected one {packageFormat} output. All outputs:\n\t{string.Join ("\n\t", outputs.Select (v => GetMetadata (v, "FullPath")))}");
+
+ var fullPath = GetMetadata (matchingOutputs [0], "FullPath");
+ Assert.That (fullPath, Does.Exist, packageFormat);
+ return AssertApplicationArtifact (outputs, fullPath, platform, packageFormat, isDirectory);
+ }
+
+ static string GetMetadata (JsonElement item, string name)
+ {
+ return item.TryGetProperty (name, out var value) ? value.GetString () ?? "" : "";
+ }
+
static List GetPostProcessingItems (string binLogPath)
+ {
+ return GetItems (binLogPath, "_PostProcessingItem");
+ }
+
+ static List GetItems (string binLogPath, string itemType)
{
var items = new Dictionary ();
foreach (var args in BinLog.ReadBuildEvents (binLogPath)) {
@@ -382,7 +640,7 @@ static List GetPostProcessingItems (string binLogPath)
continue;
if (tpea.Kind != TaskParameterMessageKind.AddItem)
continue;
- if (tpea.ItemType != "_PostProcessingItem")
+ if (tpea.ItemType != itemType)
continue;
foreach (var item in tpea.Items) {
if (item is ITaskItem taskItem)