Skip to content

Commit 777706c

Browse files
committed
[msbuild] Add validation for conflicting resources. Fixes #19029.
Fixes #19029.
1 parent 02a3f75 commit 777706c

File tree

89 files changed

+1758
-37
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+1758
-37
lines changed

msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,4 +1766,34 @@
17661766
<data name="E7152_InvalidMtouchHttpClientHandler" xml:space="preserve">
17671767
<value>Invalid value for 'MtouchHttpClientHandler' ('{0}', must be either 'NSUrlSessionHandler' or 'CFNetworkHandler' (or not set at all).</value>
17681768
</data>
1769+
1770+
<data name="W7154" xml:space="preserve">
1771+
<value>The {0} item '{1}' imported from '{2}' was ignored, because there's already an existing item from the current project with the same LogicalName ('{3}')</value>
1772+
<comment>
1773+
{0}: the name of the MSBuild item in question (BundleResource, ImageAsset, SceneKitAsset, etc.).
1774+
{1}: path to a file name (of an item)
1775+
{2}: path to an assembly
1776+
{3}: value of the LogicalName metadata of the item
1777+
</comment>
1778+
</data>
1779+
1780+
<data name="W7155" xml:space="preserve">
1781+
<value>The {0} item '{1}' imported from '{2}' was ignored, because there's another item from a different assembly ({4}) with the same LogicalName ('{3}')</value>
1782+
<comment>
1783+
{0}: the name of the MSBuild item in question (BundleResource, ImageAsset, SceneKitAsset, etc.).
1784+
{1}: path to a file name (of a resource)
1785+
{2}: path to an assembly
1786+
{3}: value of the LogicalName metadata.
1787+
{4}: comma-separated list of assemblies.
1788+
</comment>
1789+
</data>
1790+
1791+
<data name="W7156" xml:space="preserve">
1792+
<value>The {0} item '{1}' was ignored, because there's another item with the same LogicalName ('{2}')</value>
1793+
<comment>
1794+
{0}: the name of the MSBuild item in question (BundleResource, ImageAsset, SceneKitAsset, etc.).
1795+
{1}: path to a file name (of a resource)
1796+
{2}: value of the LogicalName metadata.
1797+
</comment>
1798+
</data>
17691799
</root>

msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ public override bool Execute ()
334334
items.Add (asset);
335335
}
336336

337+
imageAssets = CollectBundleResources.ComputeLogicalNameAndDetectDuplicates (this, imageAssets, ProjectDir, string.Empty, "ImageAsset", (v) => v.Item).ToArray ();
338+
337339
// clone any *.xcassets dirs that need cloning
338340
if (clones.Count > 0) {
339341
if (Directory.Exists (intermediateCloneDir))

msbuild/Xamarin.MacDev.Tasks/Tasks/CollectBundleResources.cs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.Collections.Generic;
44
using System.Diagnostics.CodeAnalysis;
5+
using System.Linq;
56

67
using Microsoft.Build.Framework;
78
using Microsoft.Build.Utilities;
@@ -97,11 +98,107 @@ bool ExecuteImpl ()
9798

9899
bundleResources.AddRange (UnpackedResources);
99100

100-
BundleResourcesWithLogicalNames = bundleResources.ToArray ();
101+
var distinctBundleResources = VerifyLogicalNameUniqueness (Log, bundleResources, "BundleResource");
102+
103+
BundleResourcesWithLogicalNames = distinctBundleResources.ToArray ();
101104

102105
return !Log.HasLoggedErrors;
103106
}
104107

108+
[return: NotNullIfNotNull (nameof (items))]
109+
public static IList<ITaskItem>? VerifyLogicalNameUniqueness (TaskLoggingHelper Log, IEnumerable<ITaskItem>? items, string itemName)
110+
{
111+
return VerifyLogicalNameUniqueness (Log, items, (v) => v, itemName);
112+
}
113+
114+
[return: NotNullIfNotNull (nameof (items))]
115+
public static IList<T>? VerifyLogicalNameUniqueness<T> (TaskLoggingHelper Log, IEnumerable<T>? items, Func<T, ITaskItem> getItem, string itemName)
116+
{
117+
if (items is null)
118+
return null;
119+
120+
var rv = new List<T> ();
121+
var groupedBundleResources = items.GroupBy (t => getItem (t).GetMetadata ("LogicalName"));
122+
var reportedItems = new HashSet<string> (); // Keep track of items we've show warnings for, to not show multiple warnings.
123+
124+
foreach (var group in groupedBundleResources) {
125+
// No/empty LogicalName is not OK.
126+
if (string.IsNullOrEmpty (group.Key)) {
127+
foreach (var t in group)
128+
Log.LogError ($"The bundle resource '{getItem (t).ItemSpec}' does not have a 'LogicalName' metadata.");
129+
continue;
130+
}
131+
// One item per LogicalName is OK.
132+
if (group.Count () == 1) {
133+
rv.AddRange (group);
134+
continue;
135+
}
136+
137+
// More than one item per LogicalName is not good at all.
138+
var notBundledInAssembly = group.Where (t => string.IsNullOrEmpty (getItem (t).GetMetadata ("BundledInAssembly")));
139+
var bundledInAssembly = group.Where (t => !string.IsNullOrEmpty (getItem (t).GetMetadata ("BundledInAssembly")));
140+
if (notBundledInAssembly.Count () == 1) {
141+
// Only one not from a library
142+
rv.AddRange (notBundledInAssembly);
143+
// warn about ignoring all the other imported ones.
144+
foreach (var t in bundledInAssembly) {
145+
var item = getItem (t);
146+
if (reportedItems.Add (item.ItemSpec)) {
147+
Log.LogWarning (7154, item.ItemSpec, MSBStrings.W7154 /* The {0} item '{1}' imported from '{2}' was ignored, because there's already an existing item from the current project with the same LogicalName ('{3}'). */, itemName, item.ItemSpec, item.GetMetadata ("BundledInAssembly"), group.Key);
148+
}
149+
}
150+
continue;
151+
} else if (notBundledInAssembly.Count () == 0) {
152+
// none from the current assembly, but multiple imported ones. Don't add any of them (to have a predictable build).
153+
// warn about ignoring all the other ones
154+
foreach (var t in bundledInAssembly) {
155+
var item = getItem (t);
156+
if (reportedItems.Add (item.ItemSpec)) {
157+
var others = bundledInAssembly.
158+
Where (v => !object.ReferenceEquals (v, t)).
159+
Select (v => Path.GetFileName (getItem (v).GetMetadata ("BundledInAssembly"))).
160+
ToArray ();
161+
Log.LogWarning (7155, item.ItemSpec, MSBStrings.W7155 /* The {0} item '{1}' imported from '{2}' was ignored, because there's another item from a different assembly ({4}) with the same LogicalName ('{3}'). */, itemName, item.ItemSpec, item.GetMetadata ("BundledInAssembly"), group.Key, string.Join (", ", others));
162+
}
163+
}
164+
continue;
165+
} else {
166+
// more than one for the current project?
167+
// don't add any of them (to have a predictable build).
168+
// warn about them all.
169+
foreach (var t in notBundledInAssembly) {
170+
var item = getItem (t);
171+
if (reportedItems.Add (item.ItemSpec)) {
172+
Log.LogWarning (7156, item.ItemSpec, MSBStrings.W7156 /* The {0} item '{1}' was ignored, because there's another item with the same LogicalName ('{2}'). */, itemName, item.ItemSpec, group.Key);
173+
}
174+
}
175+
}
176+
}
177+
178+
return rv;
179+
}
180+
181+
[return: NotNullIfNotNull (nameof (items))]
182+
public static IList<ITaskItem>? ComputeLogicalNameAndDetectDuplicates<U> (U task, IList<ITaskItem>? items, string projectDir, string resourcePrefix, string itemName) where U : Task, IHasProjectDir, IHasResourcePrefix, IHasSessionId
183+
{
184+
return ComputeLogicalNameAndDetectDuplicates<ITaskItem, U> (task, items, projectDir, resourcePrefix, itemName, (v) => v);
185+
}
186+
187+
[return: NotNullIfNotNull (nameof (items))]
188+
public static IList<T>? ComputeLogicalNameAndDetectDuplicates<T, U> (U task, IList<T>? items, string projectDir, string resourcePrefix, string itemName, Func<T, ITaskItem> getItem) where U : Task, IHasProjectDir, IHasResourcePrefix, IHasSessionId
189+
{
190+
if (items is null)
191+
return null;
192+
193+
var prefixes = BundleResource.SplitResourcePrefixes (resourcePrefix);
194+
foreach (var t in items) {
195+
var item = getItem (t);
196+
var logicalName = BundleResource.GetLogicalName (task, item);
197+
item.SetMetadata ("LogicalName", logicalName);
198+
}
199+
return CollectBundleResources.VerifyLogicalNameUniqueness<T> (task.Log, items, getItem, itemName);
200+
}
201+
105202
public static bool TryCreateItemWithLogicalName<T> (T task, ITaskItem item, [NotNullWhen (true)] out TaskItem? itemWithLogicalName) where T : Task, IHasProjectDir, IHasResourcePrefix, IHasSessionId
106203
{
107204
itemWithLogicalName = null;

msbuild/Xamarin.MacDev.Tasks/Tasks/CompileSceneKitAssets.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,19 @@ public override bool Execute ()
141141
var bundleResources = new List<ITaskItem> ();
142142
var modified = new HashSet<string> ();
143143
var items = new List<ITaskItem> ();
144+
var sceneKitAssets = CollectBundleResources.ComputeLogicalNameAndDetectDuplicates (this, SceneKitAssets, ProjectDir, ResourcePrefix, "SceneKitAsset");
144145

145-
foreach (var asset in SceneKitAssets) {
146+
foreach (var asset in sceneKitAssets) {
146147
if (!File.Exists (asset.ItemSpec))
147148
continue;
148149

149150
// get the .scnassets directory path
150151
if (!TryGetScnAssetsPath (asset.ItemSpec, out var scnassets))
151152
continue;
152153

153-
var bundleName = BundleResource.GetLogicalName (this, asset);
154-
var output = new TaskItem (Path.Combine (intermediate, bundleName));
154+
var logicalName = asset.GetMetadata ("LogicalName");
155+
var bundleName = logicalName;
156+
var output = new TaskItem (Path.Combine (intermediate, logicalName));
155157

156158
if (!modified.Contains (scnassets) && (!File.Exists (output.ItemSpec) || File.GetLastWriteTimeUtc (asset.ItemSpec) > File.GetLastWriteTimeUtc (output.ItemSpec))) {
157159
// Base the new item on @asset, to get the `DefiningProject*` metadata too
@@ -161,7 +163,7 @@ public override bool Execute ()
161163
scnassetsItem.ItemSpec = scnassets;
162164

163165
// .. and set LogicalName, the original one is for @asset
164-
if (!TryGetScnAssetsPath (bundleName, out var logicalScnAssetsPath)) {
166+
if (!TryGetScnAssetsPath (logicalName, out var logicalScnAssetsPath)) {
165167
Log.LogError (null, null, null, asset.ItemSpec, MSBStrings.E7136 /* Unable to compute the path of the *.scnassets path from the item's LogicalName '{0}'. */ , bundleName);
166168
continue;
167169
}
@@ -211,6 +213,7 @@ public override bool Execute ()
211213
foreach (var item in items) {
212214
var bundleDir = BundleResource.GetLogicalName (this, new TaskItem (item));
213215
var output = Path.Combine (intermediate, bundleDir);
216+
Log.LogMessage (MessageImportance.Low, $"Copying, item: {item.ItemSpec} logicalName (bundleDir): {bundleDir} output: {output}");
214217

215218
tasks.Add (CopySceneKitAssets (item.ItemSpec, output, intermediate));
216219
}

msbuild/Xamarin.MacDev.Tasks/Tasks/CoreMLCompiler.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,13 @@ public override bool Execute ()
164164
var mapping = new Dictionary<string, IDictionary> ();
165165
var bundleResources = new List<ITaskItem> ();
166166
var partialPlists = new List<ITaskItem> ();
167+
var models = CollectBundleResources.ComputeLogicalNameAndDetectDuplicates (this, Models, ProjectDir, ResourcePrefix, "CoreMLModel");
167168

168-
if (Models.Length > 0) {
169+
if (models.Count > 0) {
169170
Directory.CreateDirectory (coremlcOutputDir);
170171

171-
foreach (var model in Models) {
172-
var logicalName = BundleResource.GetLogicalName (this, model);
172+
foreach (var model in models) {
173+
var logicalName = model.GetMetadata ("LogicalName");
173174
var bundleName = GetPathWithoutExtension (logicalName) + ".mlmodelc";
174175
var outputPath = Path.Combine (coremlcOutputDir, bundleName);
175176
var outputDir = Path.GetDirectoryName (outputPath);

msbuild/Xamarin.MacDev.Tasks/Tasks/IBTool.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,16 +181,16 @@ static bool InterfaceDefinitionChanged (ITaskItem interfaceDefinition, ITaskItem
181181
return !LogExists (log.ItemSpec) || File.GetLastWriteTimeUtc (log.ItemSpec) < File.GetLastWriteTimeUtc (interfaceDefinition.ItemSpec);
182182
}
183183

184-
bool CompileInterfaceDefinitions (string baseManifestDir, string baseOutputDir, List<ITaskItem> compiled, IList<ITaskItem> manifests, out bool changed)
184+
bool CompileInterfaceDefinitions (IEnumerable<ITaskItem> interfaceDefinitions, string baseManifestDir, string baseOutputDir, List<ITaskItem> compiled, IList<ITaskItem> manifests, out bool changed)
185185
{
186186
var mapping = new Dictionary<string, IDictionary> ();
187187
var unique = new Dictionary<string, ITaskItem> ();
188188
var targets = GetTargetDevices ().ToList ();
189189

190190
changed = false;
191191

192-
foreach (var item in InterfaceDefinitions) {
193-
var bundleName = GetBundleRelativeOutputPath (item);
192+
foreach (var item in interfaceDefinitions) {
193+
var bundleName = item.GetMetadata ("LogicalName");
194194
var manifest = new TaskItem (Path.Combine (baseManifestDir, bundleName));
195195
var manifestDir = Path.GetDirectoryName (manifest.ItemSpec);
196196
ITaskItem duplicate;
@@ -422,11 +422,18 @@ public override bool Execute ()
422422
var compiled = new List<ITaskItem> ();
423423
bool changed;
424424

425-
if (InterfaceDefinitions.Length > 0) {
425+
foreach (var item in InterfaceDefinitions) {
426+
// Note: we overwrite any existing LogicalName property, because interface definitions always go in the app bundle's root directory.
427+
var bundleName = GetBundleRelativeOutputPath (item);
428+
item.SetMetadata ("LogicalName", bundleName);
429+
}
430+
var interfaceDefinitions = CollectBundleResources.VerifyLogicalNameUniqueness (this.Log, InterfaceDefinitions, "InterfaceDefinition").ToArray ();
431+
432+
if (interfaceDefinitions.Length > 0) {
426433
Directory.CreateDirectory (ibtoolManifestDir);
427434
Directory.CreateDirectory (ibtoolOutputDir);
428435

429-
if (!CompileInterfaceDefinitions (ibtoolManifestDir, ibtoolOutputDir, compiled, outputManifests, out changed))
436+
if (!CompileInterfaceDefinitions (interfaceDefinitions, ibtoolManifestDir, ibtoolOutputDir, compiled, outputManifests, out changed))
430437
return false;
431438

432439
if (CanLinkStoryboards) {

msbuild/Xamarin.MacDev.Tasks/Tasks/ScnTool.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ public override bool Execute ()
7676
if (ShouldExecuteRemotely ())
7777
return new TaskRunner (SessionId, BuildEngine4).RunAsync (this).Result;
7878

79+
var colladaAssets = CollectBundleResources.ComputeLogicalNameAndDetectDuplicates (this, ColladaAssets, ProjectDir, ResourcePrefix, "Collada");
7980
var listOfArguments = new List<(IList<string> Arguments, ITaskItem Input)> ();
8081
var bundleResources = new List<ITaskItem> ();
81-
foreach (var asset in ColladaAssets) {
82+
foreach (var asset in colladaAssets) {
8283
var inputScene = asset.ItemSpec;
83-
var logicalName = BundleResource.GetLogicalName (this, asset);
84+
var logicalName = asset.GetMetadata ("LogicalName");
8485
var outputScene = Path.Combine (DeviceSpecificIntermediateOutputPath, logicalName);
8586
var args = GenerateCommandLineCommands (inputScene, outputScene);
8687
listOfArguments.Add (new (args, asset));

msbuild/Xamarin.MacDev.Tasks/Tasks/TextureAtlas.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,12 @@ protected override IEnumerable<ITaskItem> EnumerateInputs ()
8787
if (AtlasTextures is null)
8888
yield break;
8989

90+
var atlasTextures = CollectBundleResources.ComputeLogicalNameAndDetectDuplicates (this, AtlasTextures, ProjectDir, ResourcePrefix, "AtlasTexture");
91+
9092
// group the atlas textures by their parent .atlas directories
91-
foreach (var item in AtlasTextures) {
92-
var vpp = BundleResource.GetVirtualProjectPath (this, item);
93-
var atlasName = Path.GetDirectoryName (vpp);
93+
foreach (var item in atlasTextures) {
9494
var logicalName = item.GetMetadata ("LogicalName");
95+
var atlasName = Path.GetDirectoryName (logicalName);
9596

9697
if (!atlases.TryGetValue (atlasName, out var atlas)) {
9798
var atlasItem = new TaskItem (atlasName);

msbuild/Xamarin.Shared/Xamarin.Shared.targets

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1296,7 +1296,6 @@ Copyright (C) 2018 Microsoft. All rights reserved.
12961296
</ItemGroup>
12971297
</Target>
12981298

1299-
<!-- TODO: check for duplicate items -->
13001299
<Target Name="_ComputeBundleResourceOutputPaths"
13011300
Condition="'$(_CanOutputAppBundle)' == 'true'"
13021301
DependsOnTargets="_CollectBundleResources;_GenerateBundleName;_DetectSigningIdentity;_ReadAppManifest">

tests/common/Configuration.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,14 @@ public static string GetBaseLibrary (TargetFramework targetFramework)
533533
return Path.Combine (GetRefDirectory (targetFramework), GetBaseLibraryName (targetFramework));
534534
}
535535

536+
public static IList<string> GetAllRuntimeIdentifiers ()
537+
{
538+
var rv = new List<string> ();
539+
foreach (var platform in GetAllPlatforms ())
540+
rv.AddRange (GetRuntimeIdentifiers (platform));
541+
return rv;
542+
}
543+
536544
public static IList<string> GetRuntimeIdentifiers (ApplePlatform platform)
537545
{
538546
return GetVariableArray ($"DOTNET_{platform.AsString ().ToUpper ()}_RUNTIME_IDENTIFIERS");

tests/common/DotNet.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ public static ExecutionResult Restore (string project, Dictionary<string, string
6363
return Execute ("restore", project, properties, false);
6464
}
6565

66-
public static ExecutionResult AssertBuild (string project, Dictionary<string, string>? properties = null, TimeSpan? timeout = null)
66+
public static ExecutionResult AssertBuild (string project, Dictionary<string, string>? properties = null, string? target = null, TimeSpan? timeout = null)
6767
{
68-
return Execute ("build", project, properties, true, timeout: timeout);
68+
return Execute ("build", project, properties, true, target: target, timeout: timeout);
6969
}
7070

7171
public static ExecutionResult AssertBuildFailure (string project, Dictionary<string, string>? properties = null)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
using Foundation;
5+
6+
namespace MySimpleApp {
7+
public class Program {
8+
static int Main (string [] args)
9+
{
10+
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly
11+
12+
Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD"));
13+
14+
return args.Length;
15+
}
16+
}
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst</TargetFramework>
5+
</PropertyGroup>
6+
<Import Project="..\shared.csproj" />
7+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../shared.mk
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TOP=../../..
2+
include $(TOP)/tests/common/shared-dotnet-test.mk
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-ios</TargetFramework>
5+
</PropertyGroup>
6+
<Import Project="..\shared.csproj" />
7+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../shared.mk
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-macos</TargetFramework>
5+
</PropertyGroup>
6+
<Import Project="..\shared.csproj" />
7+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../shared.mk

0 commit comments

Comments
 (0)