[msbuild] Deduplicate items in ComputeCodesignItems. Fixes #14522. (#14525)

We may end up trying to codesign the same item multiple times when codesigning
universal .NET apps. Avoid this by deduplicating items to codesign.

Fixes https://github.com/xamarin/xamarin-macios/issues/14522.
This commit is contained in:
Rolf Bjarne Kvinge 2022-03-29 07:37:57 +02:00 коммит произвёл GitHub
Родитель 2f099ea61d
Коммит 50192c9f96
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 277 добавлений и 56 удалений

Просмотреть файл

@ -2636,5 +2636,32 @@ namespace Xamarin.Localization.MSBuild {
return ResourceManager.GetString("W7093", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Code signing has been requested multiple times for &apos;{0}&apos;, with different metadata. The metadata for one are: &apos;{1}&apos;, while the metadata for the other are: &apos;{2}&apos;.
/// </summary>
public static string W7095 {
get {
return ResourceManager.GetString("W7095", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Code signing has been requested multiple times for &apos;{0}&apos;, with different metadata. The metadata &apos;{1}={2}&apos; has been set for one item, but not the other..
/// </summary>
public static string W7096 {
get {
return ResourceManager.GetString("W7096", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Code signing has been requested multiple times for &apos;{0}&apos;, with different metadata. The metadata &apos;{1}&apos; has been values for each item (once it&apos;s &apos;{2}&apos;, another time it&apos;s &apos;{3}&apos;)..
/// </summary>
public static string W7097 {
get {
return ResourceManager.GetString("W7097", resourceCulture);
}
}
}
}

Просмотреть файл

@ -1395,4 +1395,16 @@
<data name="E7094" xml:space="preserve">
<value>The file or directory '{0}' is not a framework nor a file within a framework.</value>
</data>
<data name="W7095" xml:space="preserve">
<value>Code signing has been requested multiple times for '{0}', with different metadata. The metadata for one are: '{1}', while the metadata for the other are: '{2}'</value>
</data>
<data name="W7096" xml:space="preserve">
<value>Code signing has been requested multiple times for '{0}', with different metadata. The metadata '{1}={2}' has been set for one item, but not the other.</value>
</data>
<data name="W7097" xml:space="preserve">
<value>Code signing has been requested multiple times for '{0}', with different metadata. The metadata '{1}' has been values for each item (once it's '{2}', another time it's '{3}').</value>
</data>
</root>

Просмотреть файл

@ -135,6 +135,36 @@ namespace Xamarin.MacDev.Tasks {
output.Add (item);
}
// We may be asked to sign the same item multiple times (in particular for universal .NET builds)
// Here we de-duplicate (based on itemspec). We also verify that the metadata is the same between
// all deduplicated items, and if not, we show a warning.
var grouped = output.GroupBy (v => v.ItemSpec);
foreach (var group in grouped) {
if (group.Count () < 2)
continue;
var all = group.ToArray ();
var firstMetadata = all [0].CloneCustomMetadataToDictionary ();
for (var i = 1; i < all.Length; i++) {
var nextMetadata = all [i].CloneCustomMetadataToDictionary ();
if (nextMetadata.Count != firstMetadata.Count) {
Log.LogWarning (MSBStrings.W7095, /* Code signing has been requested multiple times for '{0}', with different metadata. The metadata for one are: '{1}', while the metadata for the other are: '{2}' */ group.Key, string.Join (", ", firstMetadata.Keys), string.Join (", ", nextMetadata.Keys));
} else {
foreach (var kvp in firstMetadata) {
if (!nextMetadata.TryGetValue (kvp.Key, out var nextValue)) {
Log.LogWarning (MSBStrings.W7096, /* Code signing has been requested multiple times for '{0}', with different metadata. The metadata '{1}={2}' has been set for one item, but not the other. */ group.Key, kvp.Key, kvp.Value);
continue;
}
if (nextValue != kvp.Value) {
Log.LogWarning (MSBStrings.W7097, /* Code signing has been requested multiple times for '{0}', with different metadata. The metadata '{1}' has been values for each item (once it's '{2}', another time it's '{3}'). */ group.Key, kvp.Key, kvp.Value, nextValue);
continue;
}
}
}
output.Remove (all [i]);
}
}
OutputCodesignItems = output.ToArray ();
return !Log.HasLoggedErrors;

Просмотреть файл

@ -355,68 +355,220 @@ namespace Xamarin.MacDev.Tasks {
task.NativeStripItems = nativeStripItems.ToArray ();
task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform, isDotNet).ToString ();
Assert.IsTrue (task.Execute (), "Execute");
Assert.AreEqual (0, Engine.Logger.WarningsEvents.Count, "Warning Count");
var outputCodesignItems = task.OutputCodesignItems;
Assert.That (outputCodesignItems.Select (v => v.ItemSpec), Is.Unique, "Uniqueness");
var failures = new List<string> ();
var itemsFound = new List<ITaskItem> ();
foreach (var info in infos) {
var item = outputCodesignItems.SingleOrDefault (v => string.Equals (v.ItemSpec, info.ItemSpec, StringComparison.OrdinalIgnoreCase));
info.CodesignItem = item;
if (IsPlatform (info.SignedOn, platform)) {
if (item is null) {
failures.Add ($"Expected '{info.ItemSpec}' to be signed.");
continue;
}
} else {
if (item is not null) {
failures.Add ($"Did not expect '{info.ItemSpec}' to be signed.");
continue;
}
}
if (item is null)
continue;
itemsFound.Add (item);
foreach (var kvp in info.Metadata) {
var metadata = item.GetMetadata (kvp.Key);
if (metadata == string.Empty && kvp.Value != string.Empty) {
failures.Add ($"Item '{info.ItemSpec}': Expected metadata '{kvp.Key}' not found (with value '{kvp.Value}').");
} else if (!string.Equals (metadata, kvp.Value)) {
failures.Add ($"Item '{info.ItemSpec}': Expected value '{kvp.Value}' for metadata '{kvp.Key}', but got '{metadata}' instead.\nExpected: {kvp.Value}\nActual: {metadata}");
}
}
var customMetadata = item.CopyCustomMetadata ();
var unexpectedMetadata = customMetadata.Keys.ToHashSet ();
unexpectedMetadata.ExceptWith (info.Metadata.Keys);
unexpectedMetadata.Remove ("OriginalItemSpec");
foreach (var unexpected in unexpectedMetadata) {
if (string.IsNullOrEmpty (customMetadata [unexpected]))
continue;
failures.Add ($"Item '{info.ItemSpec}': Unexpected metadata '{unexpected}' with value '{customMetadata [unexpected]}'.");
}
}
var itemsNotFound = outputCodesignItems.Where (v => !itemsFound.Contains (v)).ToArray ();
foreach (var itemNotFound in itemsNotFound) {
failures.Add ($"Did not expect '{itemNotFound.ItemSpec}' to be signed.");
}
if (failures.Count > 0) {
Console.WriteLine ($"{failures.Count} failures");
foreach (var f in failures)
Console.WriteLine (f);
Console.WriteLine ($"{failures.Count} failures");
}
Assert.That (failures, Is.Empty, "Failures");
VerifyCodesigningResults (infos, task.OutputCodesignItems, platform);
} finally {
Environment.CurrentDirectory = currentDir;
}
}
[Test]
[TestCase (ApplePlatform.iOS, true)]
[TestCase (ApplePlatform.iOS, false)]
[TestCase (ApplePlatform.TVOS, true)]
[TestCase (ApplePlatform.TVOS, false)]
[TestCase (ApplePlatform.WatchOS, false)]
[TestCase (ApplePlatform.MacOSX, true)]
[TestCase (ApplePlatform.MacOSX, false)]
[TestCase (ApplePlatform.MacCatalyst, true)]
public void Duplicated (ApplePlatform platform, bool isDotNet)
{
var tmpdir = Cache.CreateTemporaryDirectory ();
var currentDir = Environment.CurrentDirectory;
try {
Environment.CurrentDirectory = tmpdir;
var codesignItems = new List<ITaskItem> ();
var codesignBundle = new List<ITaskItem> ();
string codeSignatureSubdirectory = string.Empty;
switch (platform) {
case ApplePlatform.MacCatalyst:
case ApplePlatform.MacOSX:
codeSignatureSubdirectory = "Contents/";
break;
}
var bundleAppMetadata = new Dictionary<string, string> {
{ "RequireCodeSigning", "true" },
};
var createDumpMetadata = new Dictionary<string, string> {
{ "RequireCodeSigning", "true" },
};
codesignItems = new List<ITaskItem> {
new TaskItem ("Bundle.app/Contents/MonoBundle/createdump", createDumpMetadata),
new TaskItem ("Bundle.app/Contents/MonoBundle/createdump", createDumpMetadata),
};
codesignBundle = new List<ITaskItem> {
new TaskItem ("Bundle.app", bundleAppMetadata),
};
var infos = new CodesignInfo [] {
new CodesignInfo ("Bundle.app", Platforms.All, bundleAppMetadata.Set ("CodesignStampFile", $"Bundle.app/{codeSignatureSubdirectory}_CodeSignature/CodeResources")),
new CodesignInfo ("Bundle.app/Contents/MonoBundle/createdump", Platforms.All, createDumpMetadata.Set ("CodesignStampFile", "codesign-stamp-path/Bundle.app/Contents/MonoBundle/createdump")),
};
var allFiles = infos.Select (v => v.ItemSpec).ToArray ();
Touch (tmpdir, allFiles);
var task = CreateTask<ComputeCodesignItems> ();
task.AppBundleDir = "Bundle.app";
task.CodesignBundle = codesignBundle.ToArray ();
task.CodesignItems = codesignItems.ToArray ();
task.CodesignStampPath = "codesign-stamp-path/";
task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform, isDotNet).ToString ();
Assert.IsTrue (task.Execute (), "Execute");
Assert.AreEqual (0, Engine.Logger.WarningsEvents.Count, "Warning Count");
VerifyCodesigningResults (infos, task.OutputCodesignItems, platform);
} finally {
Environment.CurrentDirectory = currentDir;
}
}
[Test]
[TestCase (ApplePlatform.iOS, true)]
[TestCase (ApplePlatform.iOS, false)]
[TestCase (ApplePlatform.TVOS, true)]
[TestCase (ApplePlatform.TVOS, false)]
[TestCase (ApplePlatform.WatchOS, false)]
[TestCase (ApplePlatform.MacOSX, true)]
[TestCase (ApplePlatform.MacOSX, false)]
[TestCase (ApplePlatform.MacCatalyst, true)]
public void DuplicatedWithDifferentMetadata (ApplePlatform platform, bool isDotNet)
{
var tmpdir = Cache.CreateTemporaryDirectory ();
var currentDir = Environment.CurrentDirectory;
try {
Environment.CurrentDirectory = tmpdir;
var codesignItems = new List<ITaskItem> ();
var codesignBundle = new List<ITaskItem> ();
string codeSignatureSubdirectory = string.Empty;
switch (platform) {
case ApplePlatform.MacCatalyst:
case ApplePlatform.MacOSX:
codeSignatureSubdirectory = "Contents/";
break;
}
var bundleAppMetadata = new Dictionary<string, string> {
{ "RequireCodeSigning", "true" },
};
var createDumpMetadata1 = new Dictionary<string, string> {
{ "RequireCodeSigning", "true" },
{ "OnlyIn1", "true" },
{ "InOneAndTwoWithDifferentValues", "1" },
};
var createDumpMetadata2 = new Dictionary<string, string> {
{ "RequireCodeSigning", "true" },
{ "OnlyIn2", "true" },
{ "InOneAndTwoWithDifferentValues", "2" },
};
var createDumpMetadata3 = new Dictionary<string, string> {
{ "RequireCodeSigning", "true" },
};
codesignItems = new List<ITaskItem> {
new TaskItem ("Bundle.app/Contents/MonoBundle/createdump", createDumpMetadata1),
new TaskItem ("Bundle.app/Contents/MonoBundle/createdump", createDumpMetadata2),
new TaskItem ("Bundle.app/Contents/MonoBundle/createdump", createDumpMetadata3),
};
codesignBundle = new List<ITaskItem> {
new TaskItem ("Bundle.app", bundleAppMetadata),
};
var infos = new CodesignInfo [] {
new CodesignInfo ("Bundle.app", Platforms.All, bundleAppMetadata.Set ("CodesignStampFile", $"Bundle.app/{codeSignatureSubdirectory}_CodeSignature/CodeResources")),
new CodesignInfo ("Bundle.app/Contents/MonoBundle/createdump", Platforms.All, createDumpMetadata1.Set ("CodesignStampFile", "codesign-stamp-path/Bundle.app/Contents/MonoBundle/createdump")),
};
var allFiles = infos.Select (v => v.ItemSpec).ToArray ();
Touch (tmpdir, allFiles);
var task = CreateTask<ComputeCodesignItems> ();
task.AppBundleDir = "Bundle.app";
task.CodesignBundle = codesignBundle.ToArray ();
task.CodesignItems = codesignItems.ToArray ();
task.CodesignStampPath = "codesign-stamp-path/";
task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform, isDotNet).ToString ();
Assert.IsTrue (task.Execute (), "Execute");
Assert.AreEqual (3, Engine.Logger.WarningsEvents.Count, "Warning Count");
Assert.AreEqual ("Code signing has been requested multiple times for 'Bundle.app/Contents/MonoBundle/createdump', with different metadata. The metadata 'OnlyIn1=true' has been set for one item, but not the other.", Engine.Logger.WarningsEvents [0].Message, "Message #0");
Assert.AreEqual ("Code signing has been requested multiple times for 'Bundle.app/Contents/MonoBundle/createdump', with different metadata. The metadata 'InOneAndTwoWithDifferentValues' has been values for each item (once it's '1', another time it's '2').", Engine.Logger.WarningsEvents [1].Message, "Message #1");
Assert.AreEqual ("Code signing has been requested multiple times for 'Bundle.app/Contents/MonoBundle/createdump', with different metadata. The metadata for one are: 'RequireCodeSigning, OnlyIn1, InOneAndTwoWithDifferentValues, CodesignStampFile', while the metadata for the other are: 'RequireCodeSigning, CodesignStampFile'", Engine.Logger.WarningsEvents [2].Message, "Message #2");
VerifyCodesigningResults (infos, task.OutputCodesignItems, platform);
} finally {
Environment.CurrentDirectory = currentDir;
}
}
void VerifyCodesigningResults (CodesignInfo [] infos, ITaskItem[] outputCodesignItems, ApplePlatform platform)
{
Assert.That (outputCodesignItems.Select (v => v.ItemSpec), Is.Unique, "Uniqueness");
var failures = new List<string> ();
var itemsFound = new List<ITaskItem> ();
foreach (var info in infos) {
var item = outputCodesignItems.SingleOrDefault (v => string.Equals (v.ItemSpec, info.ItemSpec, StringComparison.OrdinalIgnoreCase));
info.CodesignItem = item;
if (IsPlatform (info.SignedOn, platform)) {
if (item is null) {
failures.Add ($"Expected '{info.ItemSpec}' to be signed.");
continue;
}
} else {
if (item is not null) {
failures.Add ($"Did not expect '{info.ItemSpec}' to be signed.");
continue;
}
}
if (item is null)
continue;
itemsFound.Add (item);
foreach (var kvp in info.Metadata) {
var metadata = item.GetMetadata (kvp.Key);
if (metadata == string.Empty && kvp.Value != string.Empty) {
failures.Add ($"Item '{info.ItemSpec}': Expected metadata '{kvp.Key}' not found (with value '{kvp.Value}').");
} else if (!string.Equals (metadata, kvp.Value)) {
failures.Add ($"Item '{info.ItemSpec}': Expected value '{kvp.Value}' for metadata '{kvp.Key}', but got '{metadata}' instead.\nExpected: {kvp.Value}\nActual: {metadata}");
}
}
var customMetadata = item.CopyCustomMetadata ();
var unexpectedMetadata = customMetadata.Keys.ToHashSet ();
unexpectedMetadata.ExceptWith (info.Metadata.Keys);
unexpectedMetadata.Remove ("OriginalItemSpec");
foreach (var unexpected in unexpectedMetadata) {
if (string.IsNullOrEmpty (customMetadata [unexpected]))
continue;
failures.Add ($"Item '{info.ItemSpec}': Unexpected metadata '{unexpected}' with value '{customMetadata [unexpected]}'.");
}
}
var itemsNotFound = outputCodesignItems.Where (v => !itemsFound.Contains (v)).ToArray ();
foreach (var itemNotFound in itemsNotFound) {
failures.Add ($"Did not expect '{itemNotFound.ItemSpec}' to be signed.");
}
if (failures.Count > 0) {
Console.WriteLine ($"{failures.Count} failures");
foreach (var f in failures)
Console.WriteLine (f);
Console.WriteLine ($"{failures.Count} failures");
}
Assert.That (failures, Is.Empty, "Failures");
}
bool IsPlatform (Platforms platforms, ApplePlatform platform)
{
switch (platform) {