diff --git a/docs/building-apps/build-items.md b/docs/building-apps/build-items.md index 84329b453a04..ad9f1ad1f87d 100644 --- a/docs/building-apps/build-items.md +++ b/docs/building-apps/build-items.md @@ -264,6 +264,17 @@ The following metadata is set: * `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. +* `ApplicationId`: The resolved app bundle identifier. +* `ApplicationTitle`: The final `CFBundleDisplayName` value. +* `ApplicationName`: The final `CFBundleDisplayName` value, falling back to `CFBundleName` when `CFBundleDisplayName` isn't set. +* `ApplicationDisplayVersion`: The final `CFBundleShortVersionString` value. +* `ApplicationVersion`: The final `CFBundleVersion` value. + +The shared application metadata is read from the compiled app bundle +`Info.plist`, so values supplied by a custom manifest take precedence over +single-project properties. Values that are resolved or localized by Apple at a +later stage are returned as written in the compiled manifest; the build does +not choose a locale. Example: diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 941410bc8f0a..3d6a33464f12 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -571,10 +571,10 @@ A semi-colon delimited property that can be used to extend the 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. +Apple platform builds populate the common application metadata documented for +[ApplicationArtifact](build-items.md#applicationartifact) before targets in +this property execute. Extension targets can update or override that metadata, +and should only add new items when introducing additional artifacts. Example: diff --git a/dotnet/SingleProject.md b/dotnet/SingleProject.md index 9a20de442520..77bc5c5d8ce1 100644 --- a/dotnet/SingleProject.md +++ b/dotnet/SingleProject.md @@ -20,6 +20,12 @@ Info.plist in the project doesn't already contain entries for these keys): This is only enabled if the `GenerateApplicationManifest` is set to `true` (which is the default for all supported .NET versions) +Final application outputs expose these values as common metadata on +`@(ApplicationArtifact)`. The metadata is read back from the compiled +`Info.plist`, so explicit values in a custom manifest win even when the +corresponding single-project properties are set. `ApplicationName` uses +`CFBundleDisplayName` when present and otherwise falls back to `CFBundleName`. + Additionally, `$(ApplicationDisplayVersion)` will overwrite the value for `$(Version)`, so the following properties will be set with the same value: diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs index 6e70959e2d15..fb43ce623f9f 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ReadAppManifest.cs @@ -23,9 +23,15 @@ public class ReadAppManifest : XamarinTask, ITaskCallback { [Output] public string? CFBundleDisplayName { get; set; } + [Output] + public string? CFBundleName { get; set; } + [Output] public string? CFBundleIdentifier { get; set; } + [Output] + public string? CFBundleShortVersionString { get; set; } + [Output] public string? CFBundleVersion { get; set; } @@ -65,7 +71,9 @@ public override bool Execute () CFBundleExecutable = plist.GetCFBundleExecutable (); CFBundleDisplayName = plist?.GetCFBundleDisplayName (); + CFBundleName = plist?.GetCFBundleName (); CFBundleIdentifier = plist?.GetCFBundleIdentifier (); + CFBundleShortVersionString = plist?.GetCFBundleShortVersionString (); CFBundleVersion = plist?.GetCFBundleVersion (); CLKComplicationGroup = plist?.Get (ManifestKeys.CLKComplicationGroup)?.Value; diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 311e05580cc6..45a4e16980de 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -729,7 +729,9 @@ Copyright (C) 2018 Microsoft. All rights reserved. > + + @@ -3540,7 +3542,42 @@ Copyright (C) 2018 Microsoft. All rights reserved. - + + + Build; + _AddAppleApplicationArtifactMetadata; + $(GetApplicationArtifactsDependsOn); + + + + + + + + + + + + + + %(ApplicationArtifact.BundleIdentifier) + $(_ApplicationArtifactCFBundleDisplayName) + $(_ApplicationArtifactCFBundleDisplayName) + $(_ApplicationArtifactCFBundleName) + $(_ApplicationArtifactCFBundleShortVersionString) + $(_ApplicationArtifactCFBundleVersion) + + + + + false diff --git a/tests/dotnet/MySimpleAppWithArtifactMetadata/Info.plist b/tests/dotnet/MySimpleAppWithArtifactMetadata/Info.plist new file mode 100644 index 000000000000..de8dfea8de53 --- /dev/null +++ b/tests/dotnet/MySimpleAppWithArtifactMetadata/Info.plist @@ -0,0 +1,16 @@ + + + + + CFBundleDisplayName + $(PRODUCT_NAME) + CFBundleIdentifier + com.xamarin.customartifactmetadata + CFBundleName + Custom Bundle Name + CFBundleShortVersionString + 9.8.7 + CFBundleVersion + 123 + + diff --git a/tests/dotnet/MySimpleAppWithArtifactMetadata/InfoWithoutDisplayName.plist b/tests/dotnet/MySimpleAppWithArtifactMetadata/InfoWithoutDisplayName.plist new file mode 100644 index 000000000000..48f99fb5e4af --- /dev/null +++ b/tests/dotnet/MySimpleAppWithArtifactMetadata/InfoWithoutDisplayName.plist @@ -0,0 +1,14 @@ + + + + + CFBundleIdentifier + com.xamarin.customartifactmetadata + CFBundleName + Fallback Bundle Name + CFBundleShortVersionString + 9.8.7 + CFBundleVersion + 123 + + diff --git a/tests/dotnet/MySimpleAppWithArtifactMetadata/shared.csproj b/tests/dotnet/MySimpleAppWithArtifactMetadata/shared.csproj index 27aa27792cf2..b7def2f80f40 100644 --- a/tests/dotnet/MySimpleAppWithArtifactMetadata/shared.csproj +++ b/tests/dotnet/MySimpleAppWithArtifactMetadata/shared.csproj @@ -15,9 +15,10 @@ + - + <_MauiObservedAppArtifact Include="@(ApplicationArtifact)" Condition="'%(ApplicationArtifact.PackageFormat)' == 'app'" /> <_MauiObservedPackageArtifact Include="@(ApplicationArtifact)" Condition="'%(ApplicationArtifact.PackageFormat)' == '$(ExpectedAugmentedPackageFormat)'" /> diff --git a/tests/dotnet/UnitTests/PostBuildTest.cs b/tests/dotnet/UnitTests/PostBuildTest.cs index 91c459d8e75e..ec0a278c2435 100644 --- a/tests/dotnet/UnitTests/PostBuildTest.cs +++ b/tests/dotnet/UnitTests/PostBuildTest.cs @@ -138,6 +138,57 @@ public void GetApplicationArtifactsIpaTest (ApplePlatform platform, string runti AssertApplicationArtifact (outputs, pkgPath, platform, "ipa", isDirectory: false); } + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64", null, true)] + [TestCase (ApplePlatform.TVOS, "tvossimulator-arm64", null, true)] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64", null, true)] + [TestCase (ApplePlatform.MacOSX, "osx-arm64", null, true)] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64", "Info.plist", true)] + [TestCase (ApplePlatform.MacOSX, "osx-arm64", "Info.plist", false)] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64", "InfoWithoutDisplayName.plist", false)] + public void ApplicationArtifactMetadataTest (ApplePlatform platform, string runtimeIdentifiers, string? customApplicationManifest, bool generateApplicationManifest) + { + var project = "MySimpleAppWithArtifactMetadata"; + 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 ["GenerateApplicationManifest"] = generateApplicationManifest ? "true" : "false"; + if (customApplicationManifest is not null) + properties ["CustomApplicationManifest"] = customApplicationManifest; + + var outputs = GetApplicationArtifacts (project_path, properties); + var appOutput = AssertApplicationArtifact (outputs, appPath, platform, "app", isDirectory: true); + + if (customApplicationManifest is null) { + AssertApplicationMetadata ( + appOutput, + "com.xamarin.mysimpleappwithartifactmetadata", + "MySimpleAppWithArtifactMetadata", + "MySimpleAppWithArtifactMetadata", + "3.14", + "3.14"); + } else if (customApplicationManifest == "InfoWithoutDisplayName.plist") { + AssertApplicationMetadata ( + appOutput, + "com.xamarin.customartifactmetadata", + "", + "Fallback Bundle Name", + "9.8.7", + "123"); + } else { + AssertApplicationMetadata ( + appOutput, + "com.xamarin.customartifactmetadata", + "$(PRODUCT_NAME)", + "$(PRODUCT_NAME)", + "9.8.7", + "123"); + } + } + [Test] [TestCase (ApplePlatform.iOS, "ios-arm64", "ipa")] [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64", "pkg")] @@ -587,6 +638,10 @@ static JsonElement AssertApplicationArtifact (JsonElement [] outputs, string pat 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, "ApplicationId"), Is.EqualTo (GetMetadata (output, "BundleIdentifier")), "ApplicationId"); + Assert.That (GetMetadata (output, "ApplicationName"), Is.Not.Empty, "ApplicationName"); + Assert.That (GetMetadata (output, "ApplicationDisplayVersion"), Is.Not.Empty, "ApplicationDisplayVersion"); + Assert.That (GetMetadata (output, "ApplicationVersion"), Is.Not.Empty, "ApplicationVersion"); Assert.That (GetMetadata (output, "ArtifactKind"), Is.Empty, "ArtifactKind"); Assert.That (GetMetadata (output, "AppBundlePath"), Is.Empty, "AppBundlePath"); Assert.That (GetMetadata (output, "CodeSigned"), Is.Empty, "CodeSigned"); @@ -605,6 +660,17 @@ static JsonElement AssertApplicationArtifact (JsonElement [] outputs, ApplePlatf return AssertApplicationArtifact (outputs, fullPath, platform, packageFormat, isDirectory); } + static void AssertApplicationMetadata (JsonElement output, string applicationId, string applicationTitle, string applicationName, string applicationDisplayVersion, string applicationVersion) + { + Assert.Multiple (() => { + Assert.That (GetMetadata (output, "ApplicationId"), Is.EqualTo (applicationId), "ApplicationId"); + Assert.That (GetMetadata (output, "ApplicationTitle"), Is.EqualTo (applicationTitle), "ApplicationTitle"); + Assert.That (GetMetadata (output, "ApplicationName"), Is.EqualTo (applicationName), "ApplicationName"); + Assert.That (GetMetadata (output, "ApplicationDisplayVersion"), Is.EqualTo (applicationDisplayVersion), "ApplicationDisplayVersion"); + Assert.That (GetMetadata (output, "ApplicationVersion"), Is.EqualTo (applicationVersion), "ApplicationVersion"); + }); + } + static string GetMetadata (JsonElement item, string name) { return item.TryGetProperty (name, out var value) ? value.GetString () ?? "" : ""; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ReadAppManifestTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ReadAppManifestTaskTests.cs index 813da25c467e..792962e13bf0 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ReadAppManifestTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ReadAppManifestTaskTests.cs @@ -28,6 +28,26 @@ ReadAppManifest CreateTask (ApplePlatform platform = ApplePlatform.iOS, Action

{ + plist ["CFBundleDisplayName"] = "$(PRODUCT_NAME)"; + plist ["CFBundleName"] = "Bundle Name"; + plist ["CFBundleIdentifier"] = "com.xamarin.custom"; + plist ["CFBundleShortVersionString"] = "2.3.4"; + plist ["CFBundleVersion"] = "42"; + }); + ExecuteTask (task); + Assert.Multiple (() => { + Assert.That (task.CFBundleDisplayName, Is.EqualTo ("$(PRODUCT_NAME)"), "CFBundleDisplayName"); + Assert.That (task.CFBundleName, Is.EqualTo ("Bundle Name"), "CFBundleName"); + Assert.That (task.CFBundleIdentifier, Is.EqualTo ("com.xamarin.custom"), "CFBundleIdentifier"); + Assert.That (task.CFBundleShortVersionString, Is.EqualTo ("2.3.4"), "CFBundleShortVersionString"); + Assert.That (task.CFBundleVersion, Is.EqualTo ("42"), "CFBundleVersion"); + }); + } + [Test] public void MacCatalystVersionConversion () {