Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions docs/building-apps/build-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,29 @@ See also:

An item group that contains atlas textures.

## AppManifestEntry

An item group that contains entries to add to the app manifest (Info.plist).

These entries are processed last and override entries from the main app
manifest, partial app manifests, and generated values.

The following types are supported:

```xml
<ItemGroup>
<AppManifestEntry Include="BooleanKey" Type="Boolean" Value="true" />
<AppManifestEntry Include="StringKey" Type="String" Value="stringvalue" />
<AppManifestEntry Include="StringArrayKey" Type="StringArray" Value="a;b" />
<AppManifestEntry Include="StringArrayKeyWithCustomSeparator" Type="StringArray" Value="a|b" ArraySeparator="|" />
<AppManifestEntry Include="KeyToRemove" Type="Remove" />
</ItemGroup>
```

Boolean values may be `true` or `false` (case-insensitive). String arrays use a
semicolon as the default separator, which can be changed with the
`ArraySeparator` metadata.

## BGenReferencePath

The list of assembly references to pass to the `bgen` tool (binding generator).
Expand Down
20 changes: 20 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1697,6 +1697,26 @@
{1} - The assembly path.
{2} - The computed extraction path.
{3} - The intended output directory.</comment>
</data>
<data name="E7184" xml:space="preserve">
<value>Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all.</value>
<comment>Shown when an AppManifestEntry item of type Remove has a value.
{0} - The invalid value.
{1} - The app manifest entry name.
{2} - The app manifest entry type.</comment>
</data>
<data name="E7185" xml:space="preserve">
<value>Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'.</value>
<comment>Shown when an AppManifestEntry item of type Boolean has an invalid value.
{0} - The invalid value.
{1} - The app manifest entry name.
{2} - The app manifest entry type.</comment>
</data>
<data name="E7186" xml:space="preserve">
<value>Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'.</value>
<comment>Shown when an AppManifestEntry item has an unknown type.
{0} - The unknown type.
{1} - The app manifest entry name.</comment>
</data>
<data name="E0192" xml:space="preserve">
<value>The PrepareAssemblies task failed without reporting a specific error. Please rebuild with increased verbosity for more details.</value>
Expand Down
81 changes: 81 additions & 0 deletions msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

using System;
using System.Collections.Generic;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

using Xamarin.MacDev;

namespace Xamarin.MacDev.Tasks {
static class PListItemGroup {
public static void Merge (
TaskLoggingHelper log,
PDictionary dictionary,
IEnumerable<ITaskItem>? items,
Func<PString, string, PString> transformString,
string invalidRemoveValueMessage,
string invalidBooleanValueMessage,
string unknownTypeMessage)
{
if (items is null)
return;

foreach (var item in items) {
var key = item.ItemSpec;
var type = item.GetMetadata ("Type");
var value = item.GetMetadata ("Value");

switch (type.ToLowerInvariant ()) {
case "remove":
if (!string.IsNullOrEmpty (value))
log.LogError (invalidRemoveValueMessage, value, key, type);
dictionary.Remove (key);
break;
case "boolean":
if (!TryParseBooleanStrict (value, out var booleanValue)) {
log.LogError (invalidBooleanValueMessage, value, key, type);
continue;
}
dictionary [key] = new PBoolean (booleanValue);
break;
case "string":
dictionary [key] = transformString (new PString (value), key);
break;
case "stringarray":
var arraySeparator = item.GetMetadata ("ArraySeparator");
if (string.IsNullOrEmpty (arraySeparator))
arraySeparator = ";";
var array = new PArray ();
foreach (var element in value.Split (new [] { arraySeparator }, StringSplitOptions.None))
array.Add (transformString (new PString (element), key));
dictionary [key] = array;
break;
default:
log.LogError (unknownTypeMessage, type, key);
break;
}
}
}

public static bool TryParseBooleanStrict (string value, out bool result)
{
if (string.Equals (value, "true", StringComparison.OrdinalIgnoreCase)) {
result = true;
return true;
}

if (string.Equals (value, "false", StringComparison.OrdinalIgnoreCase)) {
result = false;
return true;
}

result = false;
return false;
}
}
}
16 changes: 16 additions & 0 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class CompileAppManifest : XamarinTask, IHasProjectDir, IHasResourcePrefi
// This must be an ITaskItem to copy the file to Windows for remote builds.
public ITaskItem? AppManifest { get; set; }

public ITaskItem [] AppManifestEntries { get; set; } = [];

[Required]
public string BundleExecutable { get; set; } = "";

Expand Down Expand Up @@ -157,6 +159,8 @@ public override bool Execute ()
// Merge with any partial plists...
MergePartialPlistTemplates (plist);

AddAppManifestEntries (plist);

Validation (plist);

// write the resulting app manifest
Expand All @@ -180,6 +184,18 @@ void AddXamarinVersionNumber (PDictionary plist)
plist.Add (name, dict);
}

void AddAppManifestEntries (PDictionary plist)
Comment thread
rolfbjarne marked this conversation as resolved.
{
PListItemGroup.Merge (
Log,
plist,
AppManifestEntries,
static (value, _) => value,
MSBStrings.E7184, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all. */
MSBStrings.E7185, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'. */
MSBStrings.E7186 /* Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. */);
}

void RegisterFonts (PDictionary plist)
{
if (FontFilesToRegister is null || FontFilesToRegister.Length == 0)
Expand Down
62 changes: 8 additions & 54 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,60 +277,14 @@ PDictionary MergeEntitlementDictionary (PDictionary dict, MobileProvision? profi

void AddCustomEntitlements (PDictionary dict, MobileProvision? profile)
{
if (CustomEntitlements is null)
return;

// Process any custom entitlements from the 'CustomEntitlements' item group. These are applied last, and will override anything else.
// Possible values:
// <ItemGroup>
// <CustomEntitlements Include="name.of.entitlement" Type="Boolean" Value="true" /> <!-- value can be 'false' too (case doesn't matter) -->
// <CustomEntitlements Include="name.of.entitlement" Type="String" Value="stringvalue" />
// <CustomEntitlements Include="name.of.entitlement" Type="StringArray" Value="a;b" /> <!-- array of strings, separated by semicolon -->
// <CustomEntitlements Include="name.of.entitlement" Type="StringArray" Value="a馃榿b" ArraySeparator="馃榿" /> <!-- array of strings, separated by 馃榿 -->
// <CustomEntitlements Include="name.of.entitlement" Type="Remove" /> <!-- This will remove the corresponding entitlement -->
// </ItemGroup>

foreach (var item in CustomEntitlements) {
var entitlement = item.ItemSpec;
var type = item.GetMetadata ("Type");
var value = item.GetMetadata ("Value");
switch (type.ToLowerInvariant ()) {
case "remove":
if (!string.IsNullOrEmpty (value))
Log.LogError (MSBStrings.E7102, /* Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all. */ value, entitlement, type);
dict.Remove (entitlement);
break;
case "boolean":
bool booleanValue;
if (string.Equals (value, "true", StringComparison.OrdinalIgnoreCase)) {
booleanValue = true;
} else if (string.Equals (value, "false", StringComparison.OrdinalIgnoreCase)) {
booleanValue = false;
} else {
Log.LogError (MSBStrings.E7103, /* "Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'." */ value, entitlement, type);
continue;
}

dict [entitlement] = new PBoolean (booleanValue);
break;
case "string":
dict [entitlement] = MergeEntitlementString (new PString (value), profile, entitlement == ApplicationIdentifierKey, entitlement);
break;
case "stringarray":
var arraySeparator = item.GetMetadata ("ArraySeparator");
if (string.IsNullOrEmpty (arraySeparator))
arraySeparator = ";";
var arrayContent = value.Split (new string [] { arraySeparator }, StringSplitOptions.None);
var parray = new PArray ();
foreach (var element in arrayContent)
parray.Add (MergeEntitlementString (new PString (element), profile, entitlement == ApplicationIdentifierKey, entitlement));
dict [entitlement] = parray;
break;
default:
Log.LogError (MSBStrings.E7104, /* "Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'." */ type, entitlement);
break;
}
}
PListItemGroup.Merge (
Log,
dict,
CustomEntitlements,
(value, entitlement) => MergeEntitlementString (value, profile, entitlement == ApplicationIdentifierKey, entitlement),
MSBStrings.E7102, /* Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all. */
MSBStrings.E7103, /* "Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'." */
MSBStrings.E7104 /* "Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'." */);
}

static bool AreEqual (byte [] x, byte [] y)
Expand Down
8 changes: 6 additions & 2 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
Expand Down Expand Up @@ -45,7 +44,12 @@ public override bool Execute ()
var input = Input [i];
buffer.Clear ();
foreach (var im in InputMetadata) {
buffer.AddRange (Encoding.UTF8.GetBytes (input.GetMetadata (im.ItemSpec)));
var bytes = Encoding.UTF8.GetBytes (input.GetMetadata (im.ItemSpec));
buffer.Add ((byte) (bytes.Length >> 24));
buffer.Add ((byte) (bytes.Length >> 16));
buffer.Add ((byte) (bytes.Length >> 8));
buffer.Add ((byte) bytes.Length);
buffer.AddRange (bytes);
}
var hashBytes = sha.ComputeHash (buffer.ToArray ());
var hash = string.Join ("", hashBytes.Select (b => $"{b:x2}"));
Expand Down
14 changes: 13 additions & 1 deletion msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -584,9 +584,10 @@ Copyright (C) 2018 Microsoft. All rights reserved.
* An Info.plist in their project file (by using a `None` item with filename "Info.plist" or with a `Link` metadata with filename "Info.plist"). We figure this out in the DetectAppManifest target.
* A partial plist in their project (using the `PartialAppManifest` item group). Developers can add targets to the public CollectAppManifestsDependsOn
property to run targets that add to the `PartialAppManifest` item group before we process them.
* Individual entries in the `AppManifestEntry` item group.
* Some MSBuild properties can also add values.

The precedence is: MSBuild properties can be overridden by the Info.plist, which can be overridden by a partial plist (a partial plist can also specify the "Overwrite=false" metadata to not overwrite any existing entries).
The precedence is: MSBuild properties can be overridden by the Info.plist, which can be overridden by a partial plist (a partial plist can also specify the "Overwrite=false" metadata to not overwrite any existing entries), which can be overridden by an AppManifestEntry item.

2. In the `CompileAppManifest` target we get all the inputs from above, and compute a temporary app manifest, which is written to a temporary output file.

Expand Down Expand Up @@ -623,7 +624,16 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<Target Name="_CompileAppManifestInputs" Condition="'$(IsMacEnabled)' == 'true'" >
<ItemGroup>
<_FontFilesToRegister Include="@(BundleResource)" Condition="'%(BundleResource.RegisterFont)' == 'true'" />
<_AppManifestEntryToHash Include="@(AppManifestEntry)" />
<_AppManifestEntryHashMetadata Include="Identity;Type;Value;ArraySeparator" />
</ItemGroup>
<ComputeHashForItems
Input="@(_AppManifestEntryToHash)"
InputMetadata="@(_AppManifestEntryHashMetadata)"
OutputMetadata="_Hash"
>
<Output TaskParameter="Output" ItemName="_AppManifestEntryWithHash" />
</ComputeHashForItems>

<PropertyGroup>
<_CompileAppManifestInputFile>$(DeviceSpecificIntermediateOutputPath)_CompileAppManifest.inputs</_CompileAppManifestInputFile>
Expand All @@ -638,6 +648,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<_CompileAppManifestInputLine Include="ApplicationTitle=$(ApplicationTitle)" />
<_CompileAppManifestInputLine Include="ApplicationVersion=$(ApplicationVersion)" />
<_CompileAppManifestInputLine Include="AppManifest=$(AppBundleManifest)" />
<_CompileAppManifestInputLine Include="AppManifestEntry=%(_AppManifestEntryWithHash._Hash)" />
<_CompileAppManifestInputLine Include="CompiledAppManifest=$(_TemporaryAppManifest)" />
<_CompileAppManifestInputLine Include="DefaultSdkVersion=$(_SdkVersion)" />
<_CompileAppManifestInputLine Include="FontFilesToRegister=$(_CompileAppManifestFontFilesToRegister)" />
Expand Down Expand Up @@ -688,6 +699,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
ApplicationTitle="$(ApplicationTitle)"
ApplicationVersion="$(ApplicationVersion)"
AppManifest="$(AppBundleManifest)"
AppManifestEntries="@(AppManifestEntry)"
BundleExecutable="$(_NativeExecutableName)"
CompiledAppManifest="$(_TemporaryAppManifest)"
DefaultSdkVersion="$(_SdkVersion)"
Expand Down
5 changes: 5 additions & 0 deletions tests/dotnet/MyPartialAppManifestApp/shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
<ApplicationTitle>MyPartialAppManifestApp</ApplicationTitle>
<ApplicationId>com.xamarin.mypartialappmanifestapp</ApplicationId>
<ApplicationVersion>3.14</ApplicationVersion>
<LaunchStoryboardName Condition="'$(LaunchStoryboardName)' == ''">LaunchScreen</LaunchStoryboardName>
<ManifestKey Condition="'$(ManifestKey)' == ''">a|String</ManifestKey>
<ManifestValue Condition="'$(ManifestValue)' == ''">b</ManifestValue>
</PropertyGroup>

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

<ItemGroup>
<Compile Include="../*.cs" />
<AppManifestEntry Include="$(ManifestKey)" Type="String" Value="$(ManifestValue)" />
<AppManifestEntry Include="UILaunchStoryboardName" Type="String" Value="$(LaunchStoryboardName)" />
<PartialAppManifest Include="../Partial.plist" />
</ItemGroup>
</Project>
18 changes: 18 additions & 0 deletions tests/dotnet/UnitTests/PartialAppManifestTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers)
Assert.That (infoPlist.GetString ("CFBundleVersion").Value, Is.EqualTo ("3.14"), "CFBundleVersion");
Assert.That (infoPlist.GetString ("CFBundleShortVersionString").Value, Is.EqualTo ("3.14"), "CFBundleShortVersionString");
Assert.That (infoPlist.GetString ("Something").Value, Is.EqualTo ("SomeValue"), "Something");
Assert.That (infoPlist.GetString ("UILaunchStoryboardName").Value, Is.EqualTo ("LaunchScreen"), "UILaunchStoryboardName");
Assert.That (infoPlist.GetString ("a|String").Value, Is.EqualTo ("b"), "Delimiter value");

var partialAppManifestPath = Path.Combine (Path.GetDirectoryName (project_path)!, "..", "Partial.plist");
Configuration.Touch (partialAppManifestPath);
Expand All @@ -33,6 +35,22 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers)
rv = DotNet.AssertBuild (project_path, GetDefaultProperties (runtimeIdentifiers));
allTargets = BinLog.GetAllTargets (rv.BinLogPath);
AssertTargetNotExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 2");

properties = GetDefaultProperties (runtimeIdentifiers);
properties ["ManifestKey"] = "a";
properties ["ManifestValue"] = "String|b";
rv = DotNet.AssertBuild (project_path, properties);
allTargets = BinLog.GetAllTargets (rv.BinLogPath);
AssertTargetExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 3");

infoPlist = PDictionary.OpenFile (infoPlistPath);
Assert.That (infoPlist.GetString ("UILaunchStoryboardName").Value, Is.EqualTo ("LaunchScreen"), "UILaunchStoryboardName unchanged");
Assert.That (infoPlist.ContainsKey ("a|String"), Is.False, "Old delimiter key");
Assert.That (infoPlist.GetString ("a").Value, Is.EqualTo ("String|b"), "Updated delimiter value");

rv = DotNet.AssertBuild (project_path, properties);
allTargets = BinLog.GetAllTargets (rv.BinLogPath);
AssertTargetNotExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 4");
}

[Test]
Expand Down
Loading
Loading