diff --git a/Documentation/guides/messages/README.md b/Documentation/guides/messages/README.md index e2c7747ad92..b6427a3aee5 100644 --- a/Documentation/guides/messages/README.md +++ b/Documentation/guides/messages/README.md @@ -43,6 +43,8 @@ ### XA1xxx Project Related + [XA1000](xa1000.md): There was an problem parsing {file}. This is likely due to incomplete or invalid xml. ++ [XA1001](xa1001.md): AndroidResgen: Warning while updating Resource XML '{filename}': {Message} ++ [XA1002](xa1002.md): We found a matching key '{Key}' for '{Item}'. But the casing was incorrect. Please correct the casing ### XA2xxx Linker diff --git a/Documentation/guides/messages/xa1001.md b/Documentation/guides/messages/xa1001.md new file mode 100644 index 00000000000..c37dc4ddca6 --- /dev/null +++ b/Documentation/guides/messages/xa1001.md @@ -0,0 +1,7 @@ +# Compiler Warning XA1001 + +AndroidResgen: Warning while updating Resource XML '{filename}': {Message} + +This warning is raised if we encounter an unknown issue when processing +the layout and resources. It is a generic warning that does not describe +any specific problem. The details will be in the `{Message}`. \ No newline at end of file diff --git a/Documentation/guides/messages/xa1002.md b/Documentation/guides/messages/xa1002.md new file mode 100644 index 00000000000..d08b5f51bfc --- /dev/null +++ b/Documentation/guides/messages/xa1002.md @@ -0,0 +1,13 @@ +# Compiler Error XA1000 + +This error will be emitted when we are unable to find a matching custom new in the +ResourceCaseMap string. + +As part of the build process `Namespace.CustomViewFoo` items in layout files are +replaced with `{MD5Hash}.CustomViewFoo`. We attempt to replace a couple of variants +of the `Namespace.CustomViewFoo`. One is the original casing found in the C# source +files. The other is all lowercase. If we cannot find a match we will do a case +insensitive lookup to see if there are items which do match. If we find one this +error will be raised. + +We found a matching key 'Namespace.CustomViewFoo' for 'NameSpace.CustomViewFoo'. But the casing was incorrect. Please correct the casing \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs index bf375859eec..156c00a4b3a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs @@ -92,7 +92,7 @@ public class Aapt : AsyncTask public string ResourceSymbolsTextFileDirectory { get; set; } - Dictionary resource_name_case_map = new Dictionary (); + Dictionary resource_name_case_map; AssemblyIdentityMap assemblyMap = new AssemblyIdentityMap (); string resourceDirectory; @@ -251,9 +251,7 @@ public override bool Execute () void DoExecute () { - if (ResourceNameCaseMap != null) - foreach (var arr in ResourceNameCaseMap.Split (';').Select (l => l.Split ('|')).Where (a => a.Length == 2)) - resource_name_case_map [arr [1]] = arr [0]; // lowercase -> original + resource_name_case_map = MonoAndroidHelper.LoadResourceCaseMap (ResourceNameCaseMap); assemblyMap.Load (Path.Combine (WorkingDirectory, AssemblyIdentityMapFile)); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs index 14f230e9bf4..a9ebb770d33 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs @@ -18,7 +18,7 @@ namespace Xamarin.Android.Tasks { public class Aapt2 : AsyncTask { - protected Dictionary resource_name_case_map = new Dictionary (); + protected Dictionary resource_name_case_map; public ITaskItem [] ResourceDirectories { get; set; } @@ -168,9 +168,7 @@ protected void LogEventsFromTextOutput (string singleLine, MessageImportance mes protected void LoadResourceCaseMap () { - if (ResourceNameCaseMap != null) - foreach (var arr in ResourceNameCaseMap.Split (';').Select (l => l.Split ('|')).Where (a => a.Length == 2)) - resource_name_case_map [arr [1]] = arr [0]; // lowercase -> original + resource_name_case_map = MonoAndroidHelper.LoadResourceCaseMap (ResourceNameCaseMap); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs index 79cba227bc8..99afbc32920 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs @@ -1,6 +1,7 @@ // Copyright (C) 2011 Xamarin, Inc. All rights reserved. using System; +using System.Diagnostics; using System.Collections.Generic; using System.IO; using System.Linq; @@ -20,13 +21,19 @@ public class ConvertResourcesCases : Task public string AndroidConversionFlagFile { get; set; } + public string ResourceNameCaseMap { get; set; } + + Dictionary resource_name_case_map; + public override bool Execute () { Log.LogDebugMessage ("ConvertResourcesCases Task"); Log.LogDebugMessage (" ResourceDirectories: {0}", ResourceDirectories); Log.LogDebugMessage (" AcwMapFile: {0}", AcwMapFile); Log.LogDebugMessage (" AndroidConversionFlagFile: {0}", AndroidConversionFlagFile); + Log.LogDebugMessage (" ResourceNameCaseMap: {0}", ResourceNameCaseMap); + resource_name_case_map = MonoAndroidHelper.LoadResourceCaseMap (ResourceNameCaseMap); var acw_map = MonoAndroidHelper.LoadAcwMapFile (AcwMapFile); // Look in the resource xml's for capitalized stuff and fix them @@ -71,7 +78,26 @@ void FixupResources (ITaskItem item, Dictionary acwMap) MonoAndroidHelper.SetWriteable (tmpdest); try { AndroidResource.UpdateXmlResource (resdir, tmpdest, acwMap, - ResourceDirectories.Where (s => s != item).Select(s => s.ItemSpec)); + ResourceDirectories.Where (s => s != item).Select(s => s.ItemSpec), (t, m) => { + string targetfile = file; + if (targetfile.StartsWith (resdir, StringComparison.InvariantCultureIgnoreCase)) { + targetfile = file.Substring (resdir.Length).TrimStart (Path.DirectorySeparatorChar); + if (resource_name_case_map.TryGetValue (targetfile, out string temp)) + targetfile = temp; + targetfile = Path.Combine ("Resources", targetfile); + } + switch (t) { + case TraceLevel.Error: + Log.LogCodedError ("XA1002", file: targetfile, lineNumber: 0, message: m); + break; + case TraceLevel.Warning: + Log.LogCodedWarning ("XA1001", file: targetfile, lineNumber: 0, message: m); + break; + default: + Log.LogDebugMessage (m); + break; + } + }); // We strip away an eventual UTF-8 BOM from the XML file. // This is a requirement for the Android designer because the desktop Java renderer diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ConvertResourcesCasesTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ConvertResourcesCasesTests.cs new file mode 100644 index 00000000000..7f28a058d6c --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ConvertResourcesCasesTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Xamarin.ProjectTools; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using System.Text; +using Xamarin.Android.Tasks; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Build.Tests { + + [TestFixture] + [Parallelizable (ParallelScope.Self)] + public class ConvertResourcesCasesTests : BaseTest { + [Test] + public void CheckClassIsReplacedWithMd5 () + { + var path = Path.Combine (Root, "temp", "CheckClassIsReplacedWithMd5"); + Directory.CreateDirectory (path); + var resPath = Path.Combine (path, "res"); + Directory.CreateDirectory (Path.Combine (resPath, "layout")); + File.WriteAllText (Path.Combine (resPath, "layout", "main.xml"), @" + + + + +"); + var errors = new List (); + IBuildEngine engine = new MockBuildEngine (TestContext.Out, errors); + var task = new ConvertResourcesCases { + BuildEngine = engine + }; + task.ResourceDirectories = new ITaskItem [] { + new TaskItem (resPath), + }; + task.AcwMapFile = Path.Combine (path, "acwmap.txt"); + File.WriteAllLines (task.AcwMapFile, new string [] { + "ClassLibrary1.CustomView;md5d6f7135293df7527c983d45d07471c5e.CustomTextView", + "classlibrary1.CustomView;md5d6f7135293df7527c983d45d07471c5e.CustomTextView", + }); + Assert.IsTrue (task.Execute (), "Task should have executed successfully"); + var output = File.ReadAllText (Path.Combine (resPath, "layout", "main.xml")); + StringAssert.Contains ("md5d6f7135293df7527c983d45d07471c5e.CustomTextView", output, "md5d6f7135293df7527c983d45d07471c5e.CustomTextView should exist in the main.xml"); + StringAssert.DoesNotContain ("ClassLibrary1.CustomView", output, "ClassLibrary1.CustomView should have been replaced."); + StringAssert.DoesNotContain ("classlibrary1.CustomView", output, "classlibrary1.CustomView should have been replaced."); + Directory.Delete (path, recursive: true); + } + + [Test] + public void CheckClassIsNotReplacedWithMd5 () + { + var path = Path.Combine (Root, "temp", "CheckClassIsNotReplacedWithMd5"); + Directory.CreateDirectory (path); + var resPath = Path.Combine (path, "res"); + Directory.CreateDirectory (Path.Combine (resPath, "layout")); + File.WriteAllText (Path.Combine (resPath, "layout", "main.xml"), @" + + + + +"); + var errors = new List (); + IBuildEngine engine = new MockBuildEngine (TestContext.Out, errors); + var task = new ConvertResourcesCases { + BuildEngine = engine + }; + task.ResourceDirectories = new ITaskItem [] { + new TaskItem (resPath), + }; + task.AcwMapFile = Path.Combine (path, "acwmap.txt"); + File.WriteAllLines (task.AcwMapFile, new string [] { + "ClassLibrary1.CustomView;md5d6f7135293df7527c983d45d07471c5e.CustomTextView", + "classlibrary1.CustomView;md5d6f7135293df7527c983d45d07471c5e.CustomTextView", + }); + Assert.IsTrue (task.Execute (), "Task should have executed successfully"); + var output = File.ReadAllText (Path.Combine (resPath, "layout", "main.xml")); + StringAssert.Contains ("md5d6f7135293df7527c983d45d07471c5e.CustomTextView", output, "md5d6f7135293df7527c983d45d07471c5e.CustomTextView should exist in the main.xml"); + StringAssert.DoesNotContain ("ClassLibrary1.CustomView", output, "ClassLibrary1.CustomView should have been replaced."); + StringAssert.Contains ("classLibrary1.CustomView", output, "classLibrary1.CustomView should have been replaced."); + Assert.AreEqual (1, errors.Count, "One Error should have been raised."); + Assert.AreEqual ("XA1000", errors [0].Code, "XA1000 should have been raised."); + Directory.Delete (path, recursive: true); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj index afd3ed71be2..9a80fb4318f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj @@ -84,6 +84,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs index 40420c62f54..082988ddcfe 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using System.Xml.Linq; using System.Xml.XPath; @@ -9,14 +11,14 @@ namespace Monodroid { static class AndroidResource { - public static void UpdateXmlResource (string res, string filename, Dictionary acwMap, IEnumerable additionalDirectories = null) + public static void UpdateXmlResource (string res, string filename, Dictionary acwMap, IEnumerable additionalDirectories = null, Action logMessage = null) { // use a temporary file so we only update the real file if things actually changed string tmpfile = filename + ".bk"; try { XDocument doc = XDocument.Load (filename, LoadOptions.SetLineInfo); - UpdateXmlResource (res, doc.Root, acwMap, additionalDirectories); + UpdateXmlResource (res, doc.Root, acwMap, additionalDirectories, logMessage); using (var stream = File.OpenWrite (tmpfile)) using (var xw = new LinePreservedXmlWriter (new StreamWriter (stream))) xw.WriteNode (doc.CreateNavigator (), false); @@ -27,7 +29,8 @@ public static void UpdateXmlResource (string res, string filename, Dictionary Prepend (this IEnumerable l, T another) where T : XN yield return e; } - static void UpdateXmlResource (string resourcesBasePath, XElement e, Dictionary acwMap, IEnumerable additionalDirectories = null) + static void UpdateXmlResource (string resourcesBasePath, XElement e, Dictionary acwMap, IEnumerable additionalDirectories = null, Action logMessage = null) { foreach (var elem in GetElements (e).Prepend (e)) { - TryFixCustomView (elem, acwMap); + TryFixCustomView (elem, acwMap, logMessage); } foreach (var path in fixResourcesAliasPaths) { @@ -184,8 +187,8 @@ private static bool TryFixFragment (XAttribute attr, Dictionary return false; if (attr.Name == "class" || attr.Name == android + "name") { - if (acwMap.ContainsKey (attr.Value)) { - attr.Value = acwMap[attr.Value]; + if (acwMap.TryGetValue (attr.Value, out string mappedValue)) { + attr.Value = mappedValue; return true; } @@ -193,8 +196,8 @@ private static bool TryFixFragment (XAttribute attr, Dictionary // attr.Value could be an assembly-qualified name that isn't in acw-map.txt; // see e5b1c92c, https://github.com/xamarin/xamarin-android/issues/1296#issuecomment-365091948 var n = attr.Value.Substring (0, attr.Value.IndexOf (',')); - if (acwMap.ContainsKey (n)) { - attr.Value = acwMap [n]; + if (acwMap.TryGetValue (n, out mappedValue)) { + attr.Value = mappedValue; return true; } } @@ -217,15 +220,23 @@ private static bool TryFixResAuto (XAttribute attr, Dictionary a return false; } - private static bool TryFixCustomView (XElement elem, Dictionary acwMap) + private static bool TryFixCustomView (XElement elem, Dictionary acwMap, Action logMessage = null) { // Looks for any String.Equals(x.Key, name, StringComparison.OrdinalIgnoreCase)); + if (matchingKey.Key != null) { + // we have elements with slightly different casing. + // lets issue a error. + logMessage (TraceLevel.Error, $"We found a matching key '{matchingKey.Key}' for '{name}'. But the casing was incorrect. Please correct the casing"); + } return false; } @@ -238,8 +249,7 @@ private static bool TryFixCustomClassAttribute (XAttribute attr, Dictionary LoadResourceCaseMap (string resourceCaseMap) + { + var result = new Dictionary (); + if (resourceCaseMap != null) { + foreach (var arr in resourceCaseMap.Split (';').Select (l => l.Split ('|')).Where (a => a.Length == 2)) + result [arr [1]] = arr [0]; // lowercase -> original + } + return result; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 2f8879cf3b1..5ada4913a08 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -2214,6 +2214,7 @@ because xbuild doesn't support framework reference assemblies.