From f8a31a5e99a211631bf8213bbb26d6c51b0fe4c2 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 31 Aug 2018 14:20:54 +0100 Subject: [PATCH 1/3] [Xamarin.Android.Build.Tasks] Add ConvertCustomView Fixed #2100 Currently `ConvertResourcesCases` is called twice on ALL resources referenced by the project. This is terribly inefficient. However it is required. The first pass is done before a `Compile`, this fixes up the casing for things like drawables etc ready to be processed by aapt. The second is done after `GenerateJavaStubs` which happens AFTER the `Compile` step. This is to replace any custom view references with the correct `{md5}.View` style references. This is because we replace the normal readable namespace with an md5 hash. The problem was that `ConvertResourcesCases` is doing a TON of work it doesn't really need to do the second time around. For example checking if it needs to lower case names of items. The second pass really only needs to worry about custom views. In addition to that it was also re-scanning ALL the files again. This commit introduces a new Task `ConvertCustomView`. The sole purpose of this task is to fix up the layout files which contain custom views. It does nothing else. To make this even quicker we modify `ConvertResourcesCases` to emit a mapping file (class-map.txt). This file contains items like MonoDroid.Example.MyLayout;/fullpath/to/file.xml android.support.v7.widget.ActionBarOverlayLayout;/fullpath/to/some/other/file.xml This allows us to know were the files are which contain ANY layout. With this information in conjuction with the `acw_map.txt` file will allow us to do a targeted update. So we go through the `acw_map.txt` values and fix up those files where we have entires in the `class-map.txt`. This reduces the amount of time spent processing files quite a bit. For a Blank Xamarin Forms app from a clean build. 2639 ms ConvertResourcesCases 1 calls 3 ms ConvertCustomView 1 calls Normally the `ConvertResourcesCases` would be called twice and would take a total of 5-6 seconds. --- .../Tasks/ConvertCustomView.cs | 152 ++++++++++++++++++ .../Tasks/ConvertResourcesCases.cs | 38 +++-- .../ConvertResourcesCasesTests.cs | 26 +++ .../Utilities/AndroidResource.cs | 86 ++++------ .../Utilities/MonoAndroidHelper.cs | 39 ++++- .../Xamarin.Android.Build.Tasks.csproj | 1 + .../Xamarin.Android.Common.targets | 13 +- 7 files changed, 278 insertions(+), 77 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs new file mode 100644 index 00000000000..0f677560714 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs @@ -0,0 +1,152 @@ +// Copyright (C) 2018 Microsoft, Inc. All rights reserved. + +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Monodroid; + +namespace Xamarin.Android.Tasks { + public class ConvertCustomView : Task { + + [Required] + public string CustomViewMapFile { get; set; } + + [Required] + public string AcwMapFile { get; set; } + + public string ResourceNameCaseMap { get; set; } + + public ITaskItem [] ResourceDirectories { get; set; } + + public override bool Execute () + { + var resource_name_case_map = MonoAndroidHelper.LoadResourceCaseMap (ResourceNameCaseMap); + var acw_map = MonoAndroidHelper.LoadAcwMapFile (AcwMapFile); + var customViewMap = MonoAndroidHelper.LoadCustomViewMapFile (BuildEngine4, CustomViewMapFile); + var processed = new HashSet (); + + foreach (var kvp in acw_map) { + var key = kvp.Key; + var value = kvp.Value; + if (key == value) + continue; + if (customViewMap.TryGetValue (key, out HashSet resourceFiles)) { + foreach (var file in resourceFiles) { + if (processed.Contains (file)) + continue; + if (!File.Exists (file)) + continue; + var document = XDocument.Load (file); + var e = document.Root; + bool update = false; + foreach (var elem in AndroidResource.GetElements (e).Prepend (e)) { + update |= TryFixCustomView (elem, acw_map, (t, m) => { + string targetfile = file; + ITaskItem resdir = ResourceDirectories?.FirstOrDefault (x => file.StartsWith (x.ItemSpec)) ?? null; + if (resdir != null && targetfile.StartsWith (resdir.ItemSpec, StringComparison.InvariantCultureIgnoreCase)) { + targetfile = file.Substring (resdir.ItemSpec.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; + } + }); + } + foreach (XAttribute a in AndroidResource.GetAttributes (e)) { + update |= TryFixCustomClassAttribute (a, acw_map); + update |= TryFixFragment (a, acw_map); + } + if (update) { + document.Save (file); + } + processed.Add (file); + } + } + } + + return !Log.HasLoggedErrors; + } + + static readonly XNamespace res_auto = "http://schemas.android.com/apk/res-auto"; + static readonly XNamespace android = "http://schemas.android.com/apk/res/android"; + + bool TryFixCustomClassAttribute (XAttribute attr, Dictionary acwMap) + { + /* Some attributes reference a Java class name. + * try to convert those like for TryFixCustomView + */ + if (attr.Name != (res_auto + "layout_behavior") // For custom CoordinatorLayout behavior + && (attr.Parent.Name != "transition" || attr.Name.LocalName != "class")) // For custom transitions + return false; + + if (!acwMap.TryGetValue (attr.Value, out string mappedValue)) + return false; + + attr.Value = mappedValue; + return true; + } + + bool TryFixFragment (XAttribute attr, Dictionary acwMap) + { + // Looks for any: + // 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; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs index 64030691239..894c2459e8c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs @@ -19,26 +19,31 @@ public class ConvertResourcesCases : Task [Required] public string AcwMapFile { get; set; } + [Required] + public string CustomViewMapFile { get; set; } + public string AndroidConversionFlagFile { get; set; } public string ResourceNameCaseMap { get; set; } Dictionary resource_name_case_map; + Dictionary> customViewMap; 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); + + if (CustomViewMapFile != null) + customViewMap = Xamarin.Android.Tasks.MonoAndroidHelper.LoadCustomViewMapFile (BuildEngine4, CustomViewMapFile); + // Look in the resource xml's for capitalized stuff and fix them FixupResources (acw_map); + if (customViewMap != null) + Xamarin.Android.Tasks.MonoAndroidHelper.SaveCustomViewMapFile (BuildEngine4, CustomViewMapFile, customViewMap); + return true; } @@ -80,11 +85,9 @@ void FixupResources (ITaskItem item, Dictionary acwMap) continue; } Log.LogDebugMessage (" Processing: {0} {1} > {2}", file, srcmodifiedDate, lastUpdate); - var tmpdest = Path.GetTempFileName (); - File.Copy (file, tmpdest, overwrite: true); - MonoAndroidHelper.SetWriteable (tmpdest); + MonoAndroidHelper.SetWriteable (file); try { - bool success = AndroidResource.UpdateXmlResource (resdir, tmpdest, acwMap, + bool success = AndroidResource.UpdateXmlResource (resdir, file, acwMap, resourcedirectories, (t, m) => { string targetfile = file; if (targetfile.StartsWith (resdir, StringComparison.InvariantCultureIgnoreCase)) { @@ -104,7 +107,14 @@ void FixupResources (ITaskItem item, Dictionary acwMap) Log.LogDebugMessage (m); break; } - }); + }, registerCustomView : (e, filename) => { + if (customViewMap == null) + return; + HashSet set; + if (!customViewMap.TryGetValue (e, out set)) + customViewMap.Add (e, set = new HashSet ()); + set.Add (filename); + }); if (!success) { //If we failed to write the file, a warning is logged, we should skip to the next file continue; @@ -115,11 +125,11 @@ void FixupResources (ITaskItem item, Dictionary acwMap) // doesn't support those type of BOM (it really wants the document to start // with " + + "); var errors = new List (); @@ -36,11 +38,21 @@ public void CheckClassIsReplacedWithMd5 () new TaskItem (resPath), }; task.AcwMapFile = Path.Combine (path, "acwmap.txt"); + task.CustomViewMapFile = Path.Combine (path, "classmap.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 custom = new ConvertCustomView () { + BuildEngine = engine, + CustomViewMapFile = task.CustomViewMapFile, + AcwMapFile = task.AcwMapFile, + ResourceDirectories = new ITaskItem [] { + new TaskItem (resPath), + }, + }; + Assert.IsTrue (custom.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."); @@ -59,6 +71,8 @@ public void CheckClassIsNotReplacedWithMd5 () + + "); var errors = new List (); @@ -70,17 +84,29 @@ public void CheckClassIsNotReplacedWithMd5 () new TaskItem (resPath), }; task.AcwMapFile = Path.Combine (path, "acwmap.txt"); + task.CustomViewMapFile = Path.Combine (path, "classmap.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 custom = new ConvertCustomView () { + BuildEngine = engine, + CustomViewMapFile = task.CustomViewMapFile, + AcwMapFile = task.AcwMapFile, + ResourceDirectories = new ITaskItem [] { + new TaskItem (resPath), + }, + }; + Assert.IsFalse (custom.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 ("XA1002", errors [0].Code, "XA1002 should have been raised."); + var expected = Path.Combine ("Resources", "layout", "main.xml"); + Assert.AreEqual (expected, errors [0].File, $"Error should have the \"{expected}\" path. But contained \"{errors [0].File}\""); Directory.Delete (path, recursive: true); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs index 3fbf06b93ba..e8bc4da1c11 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs @@ -11,26 +11,27 @@ namespace Monodroid { static class AndroidResource { - public static bool UpdateXmlResource (string res, string filename, Dictionary acwMap, IEnumerable additionalDirectories = null, Action logMessage = null) + public static bool UpdateXmlResource (string res, string filename, Dictionary acwMap, IEnumerable additionalDirectories = null, Action logMessage = null, Action registerCustomView = 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, logMessage); + UpdateXmlResource (res, doc.Root, acwMap, additionalDirectories, logMessage, (e) => { + registerCustomView?.Invoke (e, filename); + }); using (var stream = File.OpenWrite (tmpfile)) using (var xw = new LinePreservedXmlWriter (new StreamWriter (stream))) xw.WriteNode (doc.CreateNavigator (), false); - Xamarin.Android.Tasks.MonoAndroidHelper.CopyIfChanged (tmpfile, filename); - File.Delete (tmpfile); - return true; + + return Xamarin.Android.Tasks.MonoAndroidHelper.CopyIfChanged (tmpfile, filename); } catch (Exception e) { + logMessage?.Invoke (TraceLevel.Warning, $"AndroidResgen: Warning while updating Resource XML '{filename}': {e.Message}"); + return false; + } finally { if (File.Exists (tmpfile)) { File.Delete (tmpfile); } - logMessage?.Invoke (TraceLevel.Warning, $"AndroidResgen: Warning while updating Resource XML '{filename}': {e.Message}"); - return false; } } @@ -54,17 +55,17 @@ public static void UpdateXmlResource (XElement e, Dictionary acw UpdateXmlResource (null, e, acwMap); } - static IEnumerable Prepend (this IEnumerable l, T another) where T : XNode + internal static IEnumerable Prepend (this IEnumerable l, T another) where T : XNode { yield return another; foreach (var e in l) yield return e; } - static void UpdateXmlResource (string resourcesBasePath, XElement e, Dictionary acwMap, IEnumerable additionalDirectories = null, Action logMessage = null) + static void UpdateXmlResource (string resourcesBasePath, XElement e, Dictionary acwMap, IEnumerable additionalDirectories = null, Action logMessage = null, Action registerCustomView = null) { foreach (var elem in GetElements (e).Prepend (e)) { - TryFixCustomView (elem, acwMap, logMessage); + registerCustomView?.Invoke (elem.Name.ToString ()); } foreach (var path in fixResourcesAliasPaths) { @@ -77,14 +78,12 @@ static void UpdateXmlResource (string resourcesBasePath, XElement e, Dictionary< if (a.IsNamespaceDeclaration) continue; - if (TryFixFragment (a, acwMap)) - continue; + TryFixFragment (a, acwMap, registerCustomView); if (TryFixResAuto (a, acwMap)) continue; - if (TryFixCustomClassAttribute (a, acwMap)) - continue; + TryFixCustomClassAttribute (a, acwMap, registerCustomView); if (a.Name.Namespace != android && !(a.Name.LocalName == "layout" && a.Name.Namespace == XNamespace.None && @@ -142,7 +141,7 @@ static bool ResourceNeedsToBeLowerCased (string value, string resourceBasePath, return false; } - static IEnumerable GetAttributes (XElement e) + internal static IEnumerable GetAttributes (XElement e) { foreach (XAttribute a in e.Attributes ()) yield return a; @@ -151,7 +150,7 @@ static IEnumerable GetAttributes (XElement e) yield return a; } - static IEnumerable GetElements (XElement e) + internal static IEnumerable GetElements (XElement e) { foreach (var a in e.Elements ()) { yield return a; @@ -176,33 +175,26 @@ private static void TryFixResourceAlias (XElement elem, string resourceBasePath, } } - private static bool TryFixFragment (XAttribute attr, Dictionary acwMap) + private static void TryFixFragment (XAttribute attr, Dictionary acwMap, Action registerCustomView = null) { // Looks for any: // acwMap) @@ -219,40 +211,16 @@ private static bool TryFixResAuto (XAttribute attr, Dictionary a return false; } - 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; - } - - private static bool TryFixCustomClassAttribute (XAttribute attr, Dictionary acwMap) + private static void TryFixCustomClassAttribute (XAttribute attr, Dictionary acwMap, Action registerCustomView = null) { /* Some attributes reference a Java class name. * try to convert those like for TryFixCustomView */ if (attr.Name != (res_auto + "layout_behavior") // For custom CoordinatorLayout behavior && (attr.Parent.Name != "transition" || attr.Name.LocalName != "class")) // For custom transitions - return false; - - if (!acwMap.TryGetValue (attr.Value, out string mappedValue)) - return false; + return; - attr.Value = mappedValue; - return true; + registerCustomView?.Invoke (attr.Value); } private static string TryLowercaseValue (string value, string resourceBasePath, IEnumerable additionalDirectories) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index a007664c548..4cd6609001e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -496,13 +496,50 @@ public static Dictionary LoadAcwMapFile (string acwPath) if (!File.Exists (acwPath)) return acw_map; foreach (var s in File.ReadLines (acwPath)) { - var items = s.Split (';'); + var items = s.Split (new char[] { ';' }, count: 2); if (!acw_map.ContainsKey (items [0])) acw_map.Add (items [0], items [1]); } return acw_map; } + public static Dictionary> LoadCustomViewMapFile (IBuildEngine4 engine, string mapFile) + { + var cachedMap = (Dictionary>)engine?.GetRegisteredTaskObject (mapFile, RegisteredTaskObjectLifetime.Build); + if (cachedMap != null) + return cachedMap; + var map = new Dictionary> (); + if (!File.Exists (mapFile)) + return map; + foreach (var s in File.ReadLines (mapFile)) { + var items = s.Split (new char [] { ';' }, count: 2); + var key = items [0]; + var value = items [1]; + HashSet set; + if (!map.TryGetValue (key, out set)) + map.Add (key, set = new HashSet ()); + set.Add (value); + } + return map; + } + + public static void SaveCustomViewMapFile (IBuildEngine4 engine, string mapFile, Dictionary> map) + { + engine?.RegisterTaskObject (mapFile, map, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: false); + var temp = Path.GetTempFileName (); + try { + using (var m = new StreamWriter (temp)) { + foreach (var i in map.OrderBy (x => x.Key)) { + foreach (var v in i.Value.OrderBy (x => x)) + m.WriteLine ($"{i.Key};{v}"); + } + } + CopyIfChanged (temp, mapFile); + } finally { + File.Delete (temp); + } + } + public static string [] GetProguardEnvironmentVaribles (string proguardHome) { string proguardHomeVariable = "PROGUARD_HOME=" + proguardHome; diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index 0a43a76f68a..23c885a9e0c 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -249,6 +249,7 @@ + pdb2mdb\BitAccess.cs diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index c435aca888a..0de5d601f0c 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -44,6 +44,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + @@ -1150,6 +1151,7 @@ because xbuild doesn't support framework reference assemblies. <_AndroidDebugKeyStoreFlag>$(IntermediateOutputPath)android_debug_keystore.flag <_RemoveRegisterFlag>$(MonoAndroidIntermediateAssetsDir)shrunk\shrunk.flag <_AcwMapFile>$(IntermediateOutputPath)acw-map.txt + <_CustomViewMapFile>$(IntermediateOutputPath)customview-map.txt <_AndroidTypeMappingJavaToManaged>$(IntermediateOutputPath)android\typemap.jm <_AndroidTypeMappingManagedToJava>$(IntermediateOutputPath)android\typemap.mj $(RootNamespace) @@ -1341,6 +1343,7 @@ because xbuild doesn't support framework reference assemblies. ContinueOnError="$(DesignTimeBuild)" ResourceDirectories="@(_LibraryResourceDirectories)" AcwMapFile="$(_AcwMapFile)" + CustomViewMapFile="$(_CustomViewMapFile)" AndroidConversionFlagFile="%(_LibraryResourceDirectories.Identity)\..\compiled.flata" /> @@ -2225,11 +2230,12 @@ because xbuild doesn't support framework reference assemblies. FrameworkDirectories="$(_XATargetFrameworkDirectories);$(_XATargetFrameworkDirectories)Facades" AcwMapFile="$(_AcwMapFile)"> - @@ -3052,6 +3058,7 @@ because xbuild doesn't support framework reference assemblies. + From c9489eef703af23648b7aa2eb18a2ce26406267e Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 5 Sep 2018 16:18:16 +0100 Subject: [PATCH 2/3] Updated to unify the logging a bit --- .../Tasks/ConvertCustomView.cs | 17 +++---- .../Tasks/ConvertResourcesCases.cs | 31 +++++-------- .../Tasks/CopyAndConvertResources.cs | 45 +++++++++++++++++-- .../Utilities/MSBuildExtensions.cs | 24 ++++++++++ .../Xamarin.Android.Common.targets | 6 +++ 5 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs index 0f677560714..4da0b01d22b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs @@ -45,24 +45,17 @@ public override bool Execute () var e = document.Root; bool update = false; foreach (var elem in AndroidResource.GetElements (e).Prepend (e)) { - update |= TryFixCustomView (elem, acw_map, (t, m) => { - string targetfile = file; + update |= TryFixCustomView (elem, acw_map, (level, message) => { ITaskItem resdir = ResourceDirectories?.FirstOrDefault (x => file.StartsWith (x.ItemSpec)) ?? null; - if (resdir != null && targetfile.StartsWith (resdir.ItemSpec, StringComparison.InvariantCultureIgnoreCase)) { - targetfile = file.Substring (resdir.ItemSpec.Length).TrimStart (Path.DirectorySeparatorChar); - if (resource_name_case_map.TryGetValue (targetfile, out string temp)) - targetfile = temp; - targetfile = Path.Combine ("Resources", targetfile); - } - switch (t) { + switch (level) { case TraceLevel.Error: - Log.LogCodedError ("XA1002", file: targetfile, lineNumber: 0, message: m); + Log.FixupResourceFilenameAndLogCodedError ("XA1002", message, file, resdir.ItemSpec, resource_name_case_map); break; case TraceLevel.Warning: - Log.LogCodedWarning ("XA1001", file: targetfile, lineNumber: 0, message: m); + Log.FixupResourceFilenameAndLogCodedError ("XA1001", message, file, resdir.ItemSpec, resource_name_case_map); break; default: - Log.LogDebugMessage (m); + Log.LogDebugMessage (message); break; } }); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs index 894c2459e8c..e2c5187d461 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertResourcesCases.cs @@ -88,26 +88,19 @@ void FixupResources (ITaskItem item, Dictionary acwMap) MonoAndroidHelper.SetWriteable (file); try { bool success = AndroidResource.UpdateXmlResource (resdir, file, acwMap, - resourcedirectories, (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); + resourcedirectories, (level, message) => { + switch (level) { + case TraceLevel.Error: + Log.FixupResourceFilenameAndLogCodedError ("XA1002", message, file, resdir, resource_name_case_map); + break; + case TraceLevel.Warning: + Log.FixupResourceFilenameAndLogCodedError ("XA1001", message, file, resdir, resource_name_case_map); + break; + default: + Log.LogDebugMessage (message); + break; } - 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; - } - }, registerCustomView : (e, filename) => { + }, registerCustomView : (e, filename) => { if (customViewMap == null) return; HashSet set; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CopyAndConvertResources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CopyAndConvertResources.cs index 1d5683e9eec..da68dfdab84 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CopyAndConvertResources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CopyAndConvertResources.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -23,10 +24,18 @@ public class CopyAndConvertResources : Task [Required] public string CacheFile { get; set; } + [Required] + public string CustomViewMapFile { get; set; } + + public ITaskItem [] ResourceDirectories { get; set; } + + public string ResourceNameCaseMap { get; set; } + [Output] public ITaskItem[] ModifiedFiles { get; set; } private List modifiedFiles = new List(); + Dictionary> customViewMap; public override bool Execute () { @@ -39,6 +48,10 @@ public override bool Execute () throw new ArgumentException ("source and destination count mismatch"); var acw_map = MonoAndroidHelper.LoadAcwMapFile (AcwMapFile); + var resource_name_case_map = MonoAndroidHelper.LoadResourceCaseMap (ResourceNameCaseMap); + + if (CustomViewMapFile != null) + customViewMap = Xamarin.Android.Tasks.MonoAndroidHelper.LoadCustomViewMapFile (BuildEngine4, CustomViewMapFile); var xmlFilesToUpdate = new Dictionary (); for (int i = 0; i < SourceFiles.Length; i++) { @@ -85,11 +98,32 @@ public override bool Execute () var dstmodifiedDate = File.Exists (destfilename) ? File.GetLastWriteTimeUtc (destfilename) : DateTime.MinValue; var tmpdest = Path.GetTempFileName (); var res = Path.Combine (Path.GetDirectoryName (filename), ".."); - MonoAndroidHelper.CopyIfChanged (filename, tmpdest); - MonoAndroidHelper.SetWriteable (tmpdest); + MonoAndroidHelper.CopyIfChanged (filename, destfilename); + MonoAndroidHelper.SetWriteable (destfilename); try { - AndroidResource.UpdateXmlResource (res, tmpdest, acw_map); - if (MonoAndroidHelper.CopyIfChanged (tmpdest, destfilename)) { + var updated = AndroidResource.UpdateXmlResource (res, destfilename, acw_map, logMessage: (level, message) => { + ITaskItem resdir = ResourceDirectories?.FirstOrDefault (x => filename.StartsWith (x.ItemSpec)) ?? null; + switch (level) { + case TraceLevel.Error: + Log.FixupResourceFilenameAndLogCodedError ("XA1002", message, filename, resdir.ItemSpec, resource_name_case_map); + break; + case TraceLevel.Warning: + Log.FixupResourceFilenameAndLogCodedError ("XA1001", message, filename, resdir.ItemSpec, resource_name_case_map); + break; + default: + Log.LogDebugMessage (message); + break; + } + }, registerCustomView: (e, file) => { + if (customViewMap == null) + return; + HashSet set; + if (!customViewMap.TryGetValue (e, out set)) + customViewMap.Add (e, set = new HashSet ()); + set.Add (file); + + }); + if (updated) { if (!modifiedFiles.Any (i => i.ItemSpec == destfilename)) modifiedFiles.Add (new TaskItem (destfilename)); } @@ -102,6 +136,9 @@ public override bool Execute () Log.LogDebugTaskItems (" ModifiedFiles:", ModifiedFiles); + if (customViewMap != null) + Xamarin.Android.Tasks.MonoAndroidHelper.SaveCustomViewMapFile (BuildEngine4, CustomViewMapFile, customViewMap); + return true; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MSBuildExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MSBuildExtensions.cs index 530fd2c9bd0..0983b68ed4b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MSBuildExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MSBuildExtensions.cs @@ -188,5 +188,29 @@ public static IEnumerable Concat (params ITaskItem[][] values) yield return t; } } + + public static string FixupResourceFilename (string file, string resourceDir, Dictionary resourceNameCaseMap) + { + var targetfile = file; + if (resourceDir != null && targetfile.StartsWith (resourceDir, StringComparison.InvariantCultureIgnoreCase)) { + targetfile = file.Substring (resourceDir.Length).TrimStart (Path.DirectorySeparatorChar); + if (resourceNameCaseMap.TryGetValue (targetfile, out string temp)) + targetfile = temp; + targetfile = Path.Combine ("Resources", targetfile); + } + return targetfile; + } + + public static void FixupResourceFilenameAndLogCodedError (this TaskLoggingHelper log, string code, string message, string file, string resourceDir, Dictionary resourceNameCaseMap) + { + var targetfile = FixupResourceFilename (file, resourceDir, resourceNameCaseMap); + log.LogCodedError (code, file: targetfile, lineNumber: 0, message: message); + } + + public static void FixupResourceFilenameAndLogCodedWarning (this TaskLoggingHelper log, string code, string message, string file, string resourceDir, Dictionary resourceNameCaseMap) + { + var targetfile = FixupResourceFilename (file, resourceDir, resourceNameCaseMap); + log.LogCodedWarning (code, file: targetfile, lineNumber: 0, message: message); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 0de5d601f0c..1ca634c20f7 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1290,6 +1290,9 @@ because xbuild doesn't support framework reference assemblies. DestinationFiles="@(_AndroidResourceDest)" AcwMapFile="$(_AcwMapFile)" CacheFile="$(_AndroidResourcesCacheFile)" + CustomViewMapFile="$(_CustomViewMapFile)" + ResourceDirectories="$(MonoAndroidResDirIntermediate);@(LibraryResourceDirectories)" + ResourceNameCaseMap="$(_AndroidResourceNameCaseMap)" Condition=" '$(AndroidExplicitCrunch)' == 'True' And '$(AndroidApplication)' != '' And $(AndroidApplication)"> @@ -1342,6 +1345,7 @@ because xbuild doesn't support framework reference assemblies. Condition=" '@(_LibraryResourceDirectories)' != '' " ContinueOnError="$(DesignTimeBuild)" ResourceDirectories="@(_LibraryResourceDirectories)" + ResourceNameCaseMap="$(_AndroidResourceNameCaseMap)" AcwMapFile="$(_AcwMapFile)" CustomViewMapFile="$(_CustomViewMapFile)" AndroidConversionFlagFile="%(_LibraryResourceDirectories.Identity)\..\compiled.flata" @@ -1378,6 +1382,7 @@ because xbuild doesn't support framework reference assemblies. Date: Wed, 5 Sep 2018 16:20:22 +0100 Subject: [PATCH 3/3] Fixed up Formatting --- src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs index 4da0b01d22b..1d858c58cc0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs @@ -83,8 +83,8 @@ bool TryFixCustomClassAttribute (XAttribute attr, Dictionary acw /* Some attributes reference a Java class name. * try to convert those like for TryFixCustomView */ - if (attr.Name != (res_auto + "layout_behavior") // For custom CoordinatorLayout behavior - && (attr.Parent.Name != "transition" || attr.Name.LocalName != "class")) // For custom transitions + if (attr.Name != (res_auto + "layout_behavior") && // For custom CoordinatorLayout behavior + (attr.Parent.Name != "transition" || attr.Name.LocalName != "class")) // For custom transitions return false; if (!acwMap.TryGetValue (attr.Value, out string mappedValue))