From 43de5c78b20ca283147c38e129610f35d3581986 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Apr 2026 08:41:32 -0500 Subject: [PATCH 1/7] Bump external/xamarin-android-tools and fix NRT warnings Bump the submodule from 2fd1240 to ed6aab1 which enables nullable reference types in BaseTasks. Fix all resulting CS8600/CS8602/CS8604 warnings in callers without using the null-forgiving operator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/xamarin-android-tools | 2 +- .../Tasks/Aapt2Compile.cs | 2 +- .../Tasks/AndroidDotnetToolTask.cs | 2 +- .../Tasks/AndroidError.cs | 4 ++-- .../Tasks/AndroidWarning.cs | 4 ++-- .../Tasks/BuildAppBundle.cs | 2 +- .../Tasks/CalculateLayoutCodeBehind.cs | 2 +- .../Tasks/ConvertCustomView.cs | 4 ++-- .../Tasks/GenerateAdditionalProviderSources.cs | 3 +++ .../Tasks/GenerateLayoutBindings.cs | 6 +++--- .../Tasks/GenerateMainAndroidManifest.cs | 8 +++++++- .../Tasks/GenerateTypeMappings.cs | 3 +++ .../Tasks/GetJavaPlatformJar.cs | 12 ++++++------ .../Tasks/Legacy/ResolveAndroidTooling.cs | 4 ++-- .../Tasks/Legacy/ValidateJavaVersion.cs | 2 +- .../Tasks/ResolveAndroidTooling.cs | 8 ++++---- .../Tasks/RewriteMarshalMethods.cs | 3 +++ .../Utilities/FileResourceParser.cs | 12 ++++++------ .../Utilities/RtxtParser.cs | 4 ++-- 19 files changed, 51 insertions(+), 36 deletions(-) diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools index 2fd1240b2bf..ed6aab1e367 160000 --- a/external/xamarin-android-tools +++ b/external/xamarin-android-tools @@ -1 +1 @@ -Subproject commit 2fd1240b2bfa3bea40d7efc16ef4935734cc7f50 +Subproject commit ed6aab1e3675ac2f9e1e928c424c78de1faff674 diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Compile.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Compile.cs index 9aae0fd1a67..5675784d622 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Compile.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Compile.cs @@ -43,7 +43,7 @@ protected override int GetRequiredDaemonInstances () public async override System.Threading.Tasks.Task RunTaskAsync () { - await this.WhenAllWithLock (ResourcesToCompile ?? ResourceDirectories, ProcessDirectory); + await this.WhenAllWithLock (ResourcesToCompile ?? ResourceDirectories ?? [], ProcessDirectory); ProcessOutput (); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidDotnetToolTask.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidDotnetToolTask.cs index f7861c5bc6e..db76523528d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidDotnetToolTask.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidDotnetToolTask.cs @@ -107,7 +107,7 @@ string FindDotnet () string FindMono () { - string mono = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (MonoKey, Lifetime); + string? mono = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (MonoKey, Lifetime); if (!mono.IsNullOrEmpty ()) { Log.LogDebugMessage ($"Found cached mono via {nameof (BuildEngine4.RegisterTaskObject)}"); return mono; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs index 6b27c8fdfca..811f8f454a7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs @@ -32,8 +32,8 @@ public override bool Execute () { Log.LogCodedError ( Code, - Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture), - FormatArguments + Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture) ?? "", + FormatArguments ?? [] ); return false; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs index 6318ed08e18..c38ebcc65fa 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs @@ -32,8 +32,8 @@ public override bool Execute () { Log.LogCodedWarning ( Code, - Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture), - FormatArguments + Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture) ?? "", + FormatArguments ?? [] ); return true; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildAppBundle.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildAppBundle.cs index 98de998d321..e3366e8e224 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildAppBundle.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildAppBundle.cs @@ -107,7 +107,7 @@ public override bool RunTask () var mergedJson = json.Merge (jsonAdditionDoc); var output = mergedJson?.ToJsonString (new JsonSerializerOptions { WriteIndented = true }); - Log.LogDebugMessage ("BundleConfig.json: {0}", output); + Log.LogDebugMessage ($"BundleConfig.json: {output}"); File.WriteAllText (temp, output); //NOTE: bundletool will not overwrite diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CalculateLayoutCodeBehind.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CalculateLayoutCodeBehind.cs index c41e3c01543..b3219c30ed6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CalculateLayoutCodeBehind.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CalculateLayoutCodeBehind.cs @@ -537,7 +537,7 @@ void CreateCodeBehindTaskItems (string groupName, List layoutItems, string RegisterGroupWidgets (ICollection widgets) { string key = Guid.NewGuid ().ToString (); - LogDebugMessage ($"Registering {widgets?.Count} widgets for key {key}"); + LogDebugMessage ($"Registering {widgets.Count} widgets for key {key}"); BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (key), widgets, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: false); return key; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs index 9a6aa629e84..d15621ca4f4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs @@ -56,10 +56,10 @@ public override bool RunTask () ITaskItem? resdir = ResourceDirectories?.FirstOrDefault (x => file.StartsWith (x.ItemSpec, StringComparison.OrdinalIgnoreCase)); switch (level) { case TraceLevel.Error: - Log.FixupResourceFilenameAndLogCodedError ("XA1002", message, file, resdir?.ItemSpec, resource_name_case_map); + Log.FixupResourceFilenameAndLogCodedError ("XA1002", message, file, resdir?.ItemSpec ?? "", resource_name_case_map); break; case TraceLevel.Warning: - Log.FixupResourceFilenameAndLogCodedError ("XA1001", message, file, resdir?.ItemSpec, resource_name_case_map); + Log.FixupResourceFilenameAndLogCodedError ("XA1001", message, file, resdir?.ItemSpec ?? "", resource_name_case_map); break; default: Log.LogDebugMessage (message); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs index 36effd2b068..f798e6b2207 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs @@ -51,6 +51,9 @@ public override bool RunTask () RegisteredTaskObjectLifetime.Build ); + if (nativeCodeGenStates is null) + throw new InvalidOperationException ($"Internal error: {nameof (NativeCodeGenStateCollection)} not found"); + // We only need the first architecture, since this task is architecture-agnostic var templateCodeGenState = nativeCodeGenStates.States.First ().Value; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs index d8b58bd4366..4d4823ae372 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs @@ -205,8 +205,8 @@ void GenerateSourceForLayoutGroup (BindingGenerator generator, LayoutGroup group if (!GetRequiredMetadata (item, CalculateLayoutCodeBehind.WidgetCollectionKeyMetadata, out collectionKey)) return; - ICollection widgets = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal> (ProjectSpecificTaskObjectKey (collectionKey), RegisteredTaskObjectLifetime.Build); - if ((widgets?.Count ?? 0) == 0) { + ICollection? widgets = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal> (ProjectSpecificTaskObjectKey (collectionKey), RegisteredTaskObjectLifetime.Build); + if (widgets is null || widgets.Count == 0) { string inputPaths = String.Join ("; ", resourceItems.Select (i => i.ItemSpec)); LogCodedWarning ("XA4222", Properties.Resources.XA4222, inputPaths); return; @@ -330,7 +330,7 @@ bool GenerateSource (StreamWriter writer, BindingGenerator generator, ICollectio throw new InvalidOperationException ($"Widget {widget.Name} is of unknown type {widget.WidgetType}"); } widget.Type = decayedType; - LogCodedWarning ("XA4225", Properties.Resources.XA4225, widget.Name, className, decayedType); + LogCodedWarning ("XA4225", Properties.Resources.XA4225, widget.Name ?? "", className, decayedType ?? ""); } if (widget.Type.IsNullOrWhiteSpace ()) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs index dcece671899..de2251cfe33 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs @@ -56,6 +56,9 @@ public override bool RunTask () RegisteredTaskObjectLifetime.Build ); + if (nativeCodeGenStates is null) + throw new InvalidOperationException ($"Internal error: {nameof (NativeCodeGenState)} not found"); + // We only need the first architecture, since this task is architecture-agnostic var templateCodeGenState = nativeCodeGenStates.First ().Value; @@ -72,6 +75,9 @@ public override bool RunTask () // it to a new object that doesn't require holding open Cecil AssemblyDefinitions. var nativeCodeGenStateObject = MarshalMethodCecilAdapter.GetNativeCodeGenStateCollection (Log, nativeCodeGenStates); + if (nativeCodeGenStateObject is null) + throw new InvalidOperationException ($"Internal error: {nameof (NativeCodeGenStateCollection)} not created"); + Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenStateObject)} to {nameof (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey)}"); BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStateObject, RegisteredTaskObjectLifetime.Build); @@ -82,7 +88,7 @@ public override bool RunTask () state.Resolver.Dispose (); } - if (Log.HasLoggedErrors) { + if (Log.HasLoggedErrors && MergedAndroidManifestOutput != null) { // Ensure that on a rebuild, we don't *skip* the `_GenerateJavaStubs` target, // by ensuring that the target outputs have been deleted. Files.DeleteFile (MergedAndroidManifestOutput, Log); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs index f1323dd710f..1d6dfb5a434 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs @@ -111,6 +111,9 @@ void GenerateAllTypeMappingsFromNativeState (bool useMarshalMethods) RegisteredTaskObjectLifetime.Build ); + if (nativeCodeGenStates is null) + throw new InvalidOperationException ($"Internal error: {nameof (NativeCodeGenState)} not found"); + NativeCodeGenState? templateCodeGenState = null; foreach (var kvp in nativeCodeGenStates) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs index 8e6dfa519f3..5208a542816 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs @@ -79,11 +79,11 @@ public override bool RunTask () var failedToParseMinSdk = !int.TryParse (min_sdk.Value, out int minSdkVersion); if (failedToParseMinSdk || minSdkVersion < XABuildConfig.AndroidMinimumDotNetApiLevel.Major) { - Log.LogCodedError ("XA4216", Properties.Resources.XA4216_MinSdkVersion, min_sdk?.Value, XABuildConfig.AndroidMinimumDotNetApiLevel); + Log.LogCodedError ("XA4216", Properties.Resources.XA4216_MinSdkVersion, min_sdk.Value, XABuildConfig.AndroidMinimumDotNetApiLevel); } if (failedToParseMinSdk || minSdkVersion != supportedOsPlatformVersionAsInt) { - Log.LogCodedError ("XA1036", Properties.Resources.XA1036, min_sdk?.Value, SupportedOSPlatformVersion); + Log.LogCodedError ("XA1036", Properties.Resources.XA1036, min_sdk.Value, SupportedOSPlatformVersion ?? ""); } } if (target_sdk != null && (!int.TryParse (target_sdk.Value, out int targetSdkVersion) || targetSdkVersion < XABuildConfig.AndroidMinimumDotNetApiLevel.Major)) { @@ -127,8 +127,8 @@ string GetTargetSdkVersion (string target, XAttribute? target_sdk) // AndroidApiLevel is likely a *preview* API level; use it. Log.LogWarningForXmlNode ( code: "XA4211", - file: AndroidManifest, - node: target_sdk, + file: AndroidManifest ?? "", + node: (object?) target_sdk ?? "", message: Properties.Resources.XA4211, messageArgs: new [] { targetSdkVersion, @@ -142,8 +142,8 @@ string GetTargetSdkVersion (string target, XAttribute? target_sdk) targetSdk.Major < frameworkSdk.Major) { Log.LogWarningForXmlNode ( code: "XA4211", - file: AndroidManifest, - node: target_sdk, + file: AndroidManifest ?? "", + node: (object?) target_sdk ?? "", message: Properties.Resources.XA4211, messageArgs: new [] { targetSdkVersion, diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ResolveAndroidTooling.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ResolveAndroidTooling.cs index 74ec51f70c0..391412f0621 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ResolveAndroidTooling.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ResolveAndroidTooling.cs @@ -46,9 +46,9 @@ protected override bool Validate () int apiLevel; if (AndroidApplication && int.TryParse (AndroidApiLevel, out apiLevel)) { if (apiLevel < 30) - Log.LogCodedWarning ("XA0113", Properties.Resources.XA0113, "v11.0", "30", TargetFrameworkVersion, AndroidApiLevel); + Log.LogCodedWarning ("XA0113", Properties.Resources.XA0113, "v11.0", "30", TargetFrameworkVersion ?? "", AndroidApiLevel ?? ""); if (apiLevel < 21) - Log.LogCodedWarning ("XA0117", Properties.Resources.XA0117, TargetFrameworkVersion); + Log.LogCodedWarning ("XA0117", Properties.Resources.XA0117, TargetFrameworkVersion ?? ""); } return true; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ValidateJavaVersion.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ValidateJavaVersion.cs index cd3273c1d2e..381c93087a5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ValidateJavaVersion.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Legacy/ValidateJavaVersion.cs @@ -33,7 +33,7 @@ protected override bool ValidateJava (string javaExe, Regex versionRegex) Log.LogCodedError ("XA0031", Properties.Resources.XA0031, requiredJavaForFrameworkVersion, $"$(TargetFrameworkVersion) {TargetFrameworkVersion}"); } if (versionNumber < requiredJavaForBuildTools) { - Log.LogCodedError ("XA0032", Properties.Resources.XA0032, requiredJavaForBuildTools, AndroidSdkBuildToolsVersion); + Log.LogCodedError ("XA0032", Properties.Resources.XA0032, requiredJavaForBuildTools, AndroidSdkBuildToolsVersion ?? ""); } var latest = Version.Parse (LatestSupportedJavaVersion); if (versionNumber > latest) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs index e01cbbbebd1..c20e0bc0973 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs @@ -137,7 +137,7 @@ public override bool RunTask () if (AndroidSdkBuildToolsPath.IsNullOrEmpty ()) { Log.LogCodedError ("XA5205", Properties.Resources.XA5205, - Aapt2, AndroidSdkPath, Path.DirectorySeparatorChar, Android); + Aapt2, AndroidSdkPath ?? "", Path.DirectorySeparatorChar, Android); return false; } @@ -160,7 +160,7 @@ public override bool RunTask () } } if (Aapt2ToolPath.IsNullOrEmpty () || !File.Exists (Path.Combine (Aapt2ToolPath, aapt2Exe))) { - Log.LogCodedError ("XA0112", Properties.Resources.XA0112, Aapt2ToolPath); + Log.LogCodedError ("XA0112", Properties.Resources.XA0112, Aapt2ToolPath ?? ""); } else if (!GetAapt2Version (aapt2Exe)) { Log.LogCodedError ("XA0111", Properties.Resources.XA0111, Aapt2ToolPath); } @@ -176,7 +176,7 @@ public override bool RunTask () } if (ZipAlignPath.IsNullOrEmpty ()) { Log.LogCodedError ("XA5205", Properties.Resources.XA5205, - ZipAlign, AndroidSdkPath, Path.DirectorySeparatorChar, Android); + ZipAlign, AndroidSdkPath ?? "", Path.DirectorySeparatorChar, Android); return false; } @@ -185,7 +185,7 @@ public override bool RunTask () SequencePointsMode mode; if (!Aot.TryGetSequencePointsMode (SequencePointsMode ?? "None", out mode)) - Log.LogCodedError ("XA0104", Properties.Resources.XA0104, SequencePointsMode); + Log.LogCodedError ("XA0104", Properties.Resources.XA0104, SequencePointsMode ?? ""); AndroidSequencePointsMode = mode.ToString (); AndroidApiLevelName = MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (AndroidApiLevel); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs index e8af07e4737..319d510e98e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs @@ -84,6 +84,9 @@ public override bool RunTask () RegisteredTaskObjectLifetime.Build ); + if (nativeCodeGenStates is null) + throw new InvalidOperationException ($"Internal error: {nameof (NativeCodeGenState)} not found"); + // Parse environment files to determine configuration settings // We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed // in order to properly generate wrapper methods in the marshal methods assembly rewriter. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs index 6721cbdc3ce..8a604d48396 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs @@ -49,7 +49,7 @@ class FileResourceParser : ResourceParser public IList Parse (string resourceDirectory, IEnumerable additionalResourceDirectories, IEnumerable aarLibraries, Dictionary resourceMap) { - Log.LogDebugMessage ($"Parsing Directory {resourceDirectory}"); + Log?.LogDebugMessage ($"Parsing Directory {resourceDirectory}"); publicXml = LoadPublicXml (); var result = new List (); Dictionary> resources = new Dictionary> (); @@ -212,7 +212,7 @@ int GetId (ICollection resources, string identifier) void ProcessResourceFile (string file, Dictionary> resources, bool processXml = true) { - Log.LogDebugMessage ($"{nameof(ProcessResourceFile)} {file}"); + Log?.LogDebugMessage ($"{nameof(ProcessResourceFile)} {file}"); var fileName = Path.GetFileNameWithoutExtension (file); if (fileName.IsNullOrEmpty ()) return; @@ -265,7 +265,7 @@ void CreateResourceField (string root, string id, Dictionary> resources) { - Log.LogDebugMessage ($"{nameof(ProcessStyleable)}"); + Log?.LogDebugMessage ($"{nameof(ProcessStyleable)}"); string? topName = null; List fields = new List (); List attribs = new List (); @@ -374,7 +374,7 @@ void ProcessXmlFile (string file, Dictionary> resources) void ProcessXmlFile (XmlReader reader, Dictionary> resources) { - Log.LogDebugMessage ($"{nameof(ProcessXmlFile)}"); + Log?.LogDebugMessage ($"{nameof(ProcessXmlFile)}"); while (reader.Read ()) { if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment) continue; @@ -422,7 +422,7 @@ void ProcessXmlFile (XmlReader reader, Dictionary> resour Identifier = inflateId, Id = -1, }; - Log.LogDebugMessage ($"Adding 1 {r}"); + Log?.LogDebugMessage ($"Adding 1 {r}"); resources[r.ResourceTypeName].Add (r); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs index 81c7e97749d..25359515376 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs @@ -113,11 +113,11 @@ void ProcessRtxtFile (string file, List result) lineNumber++; var items = line.Split (EmptyChar, 4); if (items.Length < 4) { - log.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it does not have the correct number of elements."); + log?.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it does not have the correct number of elements."); continue; } if (ValidChars.IsMatch (items [3])) { - log.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it contains invalid characters."); + log?.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it contains invalid characters."); continue; } int value = items [1] != "styleable" ? Convert.ToInt32 (items [3].Trim (), 16) : -1; From debc21f3818d4f9a32b30d086b365d047bc3e4c7 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Apr 2026 09:18:09 -0500 Subject: [PATCH 2/7] Make Log non-nullable in ResourceParser and RtxtParser Use `required` on ResourceParser.Log so it must be set via object initializer, eliminating the need for Log?. Pass log and map as parameters to RtxtParser.ProcessRtxtFile instead of storing them in nullable fields, removing the null-conditional calls and the runtime null check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/FileResourceParser.cs | 14 +++++++------- .../Utilities/ResourceParser.cs | 2 +- .../Utilities/RtxtParser.cs | 16 ++++------------ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs index 8a604d48396..db5fbd91d9a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs @@ -49,7 +49,7 @@ class FileResourceParser : ResourceParser public IList Parse (string resourceDirectory, IEnumerable additionalResourceDirectories, IEnumerable aarLibraries, Dictionary resourceMap) { - Log?.LogDebugMessage ($"Parsing Directory {resourceDirectory}"); + Log.LogDebugMessage ($"Parsing Directory {resourceDirectory}"); publicXml = LoadPublicXml (); var result = new List (); Dictionary> resources = new Dictionary> (); @@ -212,7 +212,7 @@ int GetId (ICollection resources, string identifier) void ProcessResourceFile (string file, Dictionary> resources, bool processXml = true) { - Log?.LogDebugMessage ($"{nameof(ProcessResourceFile)} {file}"); + Log.LogDebugMessage ($"{nameof(ProcessResourceFile)} {file}"); var fileName = Path.GetFileNameWithoutExtension (file); if (fileName.IsNullOrEmpty ()) return; @@ -265,7 +265,7 @@ void CreateResourceField (string root, string id, Dictionary> resources) { - Log?.LogDebugMessage ($"{nameof(ProcessStyleable)}"); + Log.LogDebugMessage ($"{nameof(ProcessStyleable)}"); string? topName = null; List fields = new List (); List attribs = new List (); @@ -374,7 +374,7 @@ void ProcessXmlFile (string file, Dictionary> resources) void ProcessXmlFile (XmlReader reader, Dictionary> resources) { - Log?.LogDebugMessage ($"{nameof(ProcessXmlFile)}"); + Log.LogDebugMessage ($"{nameof(ProcessXmlFile)}"); while (reader.Read ()) { if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment) continue; @@ -389,7 +389,7 @@ void ProcessXmlFile (XmlReader reader, Dictionary> resour try { ProcessStyleable (reader.ReadSubtree (), resources); } catch (Exception ex) { - Log?.LogErrorFromException (ex); + Log.LogErrorFromException (ex); } continue; } @@ -422,7 +422,7 @@ void ProcessXmlFile (XmlReader reader, Dictionary> resour Identifier = inflateId, Id = -1, }; - Log?.LogDebugMessage ($"Adding 1 {r}"); + Log.LogDebugMessage ($"Adding 1 {r}"); resources[r.ResourceTypeName].Add (r); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs index d8f4a6db243..a1bdae8437e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs @@ -13,7 +13,7 @@ namespace Xamarin.Android.Tasks { class ResourceParser { - public TaskLoggingHelper? Log { get; set; } + public required TaskLoggingHelper Log { get; set; } internal int ToInt32 (string value, int @base) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs index 25359515376..9b2f0fd9841 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs @@ -68,9 +68,6 @@ public class RtxtParser { static readonly char[] CommaChar = new char [] { ',' }; static readonly Regex ValidChars = new Regex (@"([^a-f0-9x, \{\}])+", RegexOptions.Compiled | RegexOptions.IgnoreCase); - TaskLoggingHelper? log; - Dictionary? map; - public static readonly HashSet knownTypes = new HashSet () { "anim", "animator", @@ -98,32 +95,27 @@ public class RtxtParser { public IEnumerable Parse (string file, TaskLoggingHelper logger, Dictionary mapping) { - log = logger; - map = mapping; var result = new List (DefaultCapacity); if (File.Exists (file)) - ProcessRtxtFile (file, result); + ProcessRtxtFile (file, result, logger, mapping); return result; } - void ProcessRtxtFile (string file, List result) + void ProcessRtxtFile (string file, List result, TaskLoggingHelper log, Dictionary map) { int lineNumber = 0; foreach (var line in File.ReadLines (file) ) { lineNumber++; var items = line.Split (EmptyChar, 4); if (items.Length < 4) { - log?.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it does not have the correct number of elements."); + log.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it does not have the correct number of elements."); continue; } if (ValidChars.IsMatch (items [3])) { - log?.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it contains invalid characters."); + log.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it contains invalid characters."); continue; } int value = items [1] != "styleable" ? Convert.ToInt32 (items [3].Trim (), 16) : -1; - if (map == null || log == null) { - throw new InvalidOperationException("Parser not initialized with logger and mapping"); - } string itemName = ResourceIdentifier.GetResourceName(ResourceParser.GetNestedTypeName (items [1]), items [2], map, log); if (knownTypes.Contains (items [1])) { if (items [1] != "styleable") { From f3826a49a80c556657233b6d293c1f5eaa851638 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Apr 2026 12:02:02 -0500 Subject: [PATCH 3/7] Use constructor parameter instead of required for netstandard2.0 The `required` keyword needs CompilerFeatureRequiredAttribute which is only available in net7.0+, not netstandard2.0. Use a constructor parameter for ResourceParser.Log instead, and chain it through FileResourceParser, JavaResourceParser, and ManagedResourceParser. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GenerateResourceDesigner.cs | 4 ++-- src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs | 2 +- .../Tasks/ManagedResourceParserTests.cs | 3 +-- .../Utilities/FileResourceParser.cs | 2 ++ .../Utilities/JavaResourceParser.cs | 5 ++--- .../Utilities/ManagedResourceParser.cs | 1 + .../Utilities/ResourceParser.cs | 7 ++++++- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs index fa7b09c5288..4fb35a3c26b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs @@ -83,13 +83,13 @@ public override bool RunTask () // Parse out the resources from the R.java file CodeTypeDeclaration resources; if (UseManagedResourceGenerator) { - var parser = new ManagedResourceParser () { Log = Log, JavaPlatformDirectory = javaPlatformDirectory, ResourceFlagFile = ResourceFlagFile }; + var parser = new ManagedResourceParser (Log) { JavaPlatformDirectory = javaPlatformDirectory, ResourceFlagFile = ResourceFlagFile }; resources = parser.Parse (ResourceDirectory, RTxtFile ?? string.Empty, AdditionalResourceDirectories?.Select (x => x.ItemSpec), IsApplication, resource_fixup); } else { if (JavaResgenInputFile == null) { throw new ArgumentNullException (nameof (JavaResgenInputFile)); } - var parser = new JavaResourceParser () { Log = Log }; + var parser = new JavaResourceParser (Log); resources = parser.Parse (JavaResgenInputFile, IsApplication, resource_fixup); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs index ec60bbb2d1f..962cc88d3a9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs @@ -33,7 +33,7 @@ public override bool RunTask () var resource_fixup = MonoAndroidHelper.LoadMapFile (BuildEngine4, Path.GetFullPath (CaseMapFile), StringComparer.OrdinalIgnoreCase); var javaPlatformDirectory = JavaPlatformJarPath.IsNullOrEmpty () ? "" : Path.GetDirectoryName (JavaPlatformJarPath); - var parser = new FileResourceParser () { Log = Log, JavaPlatformDirectory = javaPlatformDirectory, ResourceFlagFile = ResourceFlagFile}; + var parser = new FileResourceParser (Log) { JavaPlatformDirectory = javaPlatformDirectory, ResourceFlagFile = ResourceFlagFile}; var resources = parser.Parse (ResourceDirectory, AdditionalResourceDirectories ?? [], AarLibraries ?? [], resource_fixup); // only update if it changed. diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs index bb58e38a23f..534ec6bfcee 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs @@ -654,8 +654,7 @@ public void CheckPerformanceOfManagedParser () long totalMS = 0; int i; for (i = 0; i < 100; i++) { - var parser = new ManagedResourceParser () { - Log = loggingHelper, + var parser = new ManagedResourceParser (loggingHelper) { JavaPlatformDirectory = Path.Combine (AndroidSdkDirectory, "platforms", $"android-{platform.Major}"), ResourceFlagFile = flagFile, }; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs index db5fbd91d9a..3bbdee7d6e5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs @@ -20,6 +20,8 @@ namespace Xamarin.Android.Tasks { class FileResourceParser : ResourceParser { + public FileResourceParser (TaskLoggingHelper log) : base (log) { } + public string? JavaPlatformDirectory { get; set; } public string? ResourceFlagFile { get; set; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs index 400a6b9c61f..d88ef2ae62b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs @@ -12,14 +12,13 @@ namespace Xamarin.Android.Tasks { class JavaResourceParser : ResourceParser { + public JavaResourceParser (TaskLoggingHelper log) : base (log) { } + public CodeTypeDeclaration Parse (string file, bool isApp, Dictionary resourceMap) { if (!File.Exists (file)) throw new InvalidOperationException ("Specified Java resource file was not found: " + file); - if (Log == null) - throw new InvalidOperationException ("Log property must be set before calling Parse"); - CodeTypeDeclaration? resources = null; using (var reader = File.OpenText (file)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs index 0861e016444..acd18bb3a64 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs @@ -18,6 +18,7 @@ namespace Xamarin.Android.Tasks { class ManagedResourceParser : FileResourceParser { + public ManagedResourceParser (TaskLoggingHelper log) : base (log) { } class CompareTuple : IComparer<(int Key, CodeMemberField Value)> { public int Compare((int Key, CodeMemberField Value) x, (int Key, CodeMemberField Value) y) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs index a1bdae8437e..79e5aff1d4d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs @@ -13,7 +13,12 @@ namespace Xamarin.Android.Tasks { class ResourceParser { - public required TaskLoggingHelper Log { get; set; } + public TaskLoggingHelper Log { get; } + + public ResourceParser (TaskLoggingHelper log) + { + Log = log; + } internal int ToInt32 (string value, int @base) { From e27ea6c0884e784fbd5b14dbb297fad69cc43f41 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Apr 2026 14:40:56 -0500 Subject: [PATCH 4/7] Add missing using for TaskLoggingHelper in ManagedResourceParser Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/ManagedResourceParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs index acd18bb3a64..e270004f214 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Text; using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Utilities; namespace Xamarin.Android.Tasks From 1dc52a7edaad4cd68a01e39295fd66a2e24141dc Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Apr 2026 15:21:03 -0500 Subject: [PATCH 5/7] Fix JavaResourceParser constructor and add missing using Move Parser field initialization into the constructor that takes TaskLoggingHelper, removing the old parameterless constructor. Also remove Log! usages now that Log is guaranteed non-null. Add missing using System in RewriteMarshalMethods. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/RewriteMarshalMethods.cs | 1 + .../Utilities/JavaResourceParser.cs | 72 ++++++++----------- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs index 319d510e98e..029e1140dbb 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Collections.Concurrent; using System.Linq; using Microsoft.Android.Build.Tasks; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs index d88ef2ae62b..bb186bc970e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs @@ -12,47 +12,9 @@ namespace Xamarin.Android.Tasks { class JavaResourceParser : ResourceParser { - public JavaResourceParser (TaskLoggingHelper log) : base (log) { } - - public CodeTypeDeclaration Parse (string file, bool isApp, Dictionary resourceMap) - { - if (!File.Exists (file)) - throw new InvalidOperationException ("Specified Java resource file was not found: " + file); - - CodeTypeDeclaration? resources = null; - - using (var reader = File.OpenText (file)) { - string line; - - while ((line = reader.ReadLine ()) != null) { - var info = Parser.Select (p => new { Match = p.Key.Match (line), Handler = p.Value }).FirstOrDefault (x => x.Match.Success); - - if (info == null) - continue; - - resources = info.Handler (info.Match, isApp, resources, resourceMap); - } - } - - return resources ?? new CodeTypeDeclaration ("Resource") { IsPartial = true }; - } - - static KeyValuePair, CodeTypeDeclaration>> Parse (string regex, Func, CodeTypeDeclaration> f) - { - return new KeyValuePair, CodeTypeDeclaration>> (new Regex (regex), f); - } - - // public finall class R { - // public static fnal class string|anim|styleable|etc. { - // public static final int field = 0xZZ; - // public static final int [] array = { - // 0xXX, 0xYY, 0xZZ - // } - // } - // } List, CodeTypeDeclaration>>> Parser; - public JavaResourceParser () + public JavaResourceParser (TaskLoggingHelper log) : base (log) { Parser = new List, CodeTypeDeclaration>>> () { Parse ("^public final class R {", @@ -89,7 +51,7 @@ public JavaResourceParser () (m, app, g, map) => { g ??= new CodeTypeDeclaration ("Resource") { IsPartial = true }; var name = ((CodeTypeDeclaration) g.Members [g.Members.Count-1]).Name; - var f = new CodeMemberField (typeof (int), ResourceIdentifier.GetResourceName (name, m.Groups[1].Value, map, Log!)) { + var f = new CodeMemberField (typeof (int), ResourceIdentifier.GetResourceName (name, m.Groups[1].Value, map, Log)) { Attributes = app ? MemberAttributes.Const | MemberAttributes.Public : MemberAttributes.Static | MemberAttributes.Public, InitExpression = new CodePrimitiveExpression (ToInt32 (m.Groups [2].Value, m.Groups [2].Value.IndexOf ("0x", StringComparison.Ordinal) == 0 ? 16 : 10)), Comments = { @@ -103,7 +65,7 @@ public JavaResourceParser () (m, app, g, map) => { g ??= new CodeTypeDeclaration ("Resource") { IsPartial = true }; var name = ((CodeTypeDeclaration) g.Members [g.Members.Count-1]).Name; - var f = new CodeMemberField (typeof (int[]), ResourceIdentifier.GetResourceName (name, m.Groups[1].Value, map, Log!)) { + var f = new CodeMemberField (typeof (int[]), ResourceIdentifier.GetResourceName (name, m.Groups[1].Value, map, Log)) { // pity I can't make the member readonly... Attributes = MemberAttributes.Public | MemberAttributes.Static, }; @@ -126,5 +88,33 @@ public JavaResourceParser () }), }; } + + public CodeTypeDeclaration Parse (string file, bool isApp, Dictionary resourceMap) + { + if (!File.Exists (file)) + throw new InvalidOperationException ("Specified Java resource file was not found: " + file); + + CodeTypeDeclaration? resources = null; + + using (var reader = File.OpenText (file)) { + string line; + + while ((line = reader.ReadLine ()) != null) { + var info = Parser.Select (p => new { Match = p.Key.Match (line), Handler = p.Value }).FirstOrDefault (x => x.Match.Success); + + if (info == null) + continue; + + resources = info.Handler (info.Match, isApp, resources, resourceMap); + } + } + + return resources ?? new CodeTypeDeclaration ("Resource") { IsPartial = true }; + } + + static KeyValuePair, CodeTypeDeclaration>> Parse (string regex, Func, CodeTypeDeclaration> f) + { + return new KeyValuePair, CodeTypeDeclaration>> (new Regex (regex), f); + } } } From f82b5ef1c0302441fd2dd4ce67ab211479d8d3ee Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 29 Apr 2026 15:24:12 -0500 Subject: [PATCH 6/7] Restore original method order in JavaResourceParser Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/JavaResourceParser.cs | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs index bb186bc970e..1f1921fc40a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs @@ -12,6 +12,42 @@ namespace Xamarin.Android.Tasks { class JavaResourceParser : ResourceParser { + public CodeTypeDeclaration Parse (string file, bool isApp, Dictionary resourceMap) + { + if (!File.Exists (file)) + throw new InvalidOperationException ("Specified Java resource file was not found: " + file); + + CodeTypeDeclaration? resources = null; + + using (var reader = File.OpenText (file)) { + string line; + + while ((line = reader.ReadLine ()) != null) { + var info = Parser.Select (p => new { Match = p.Key.Match (line), Handler = p.Value }).FirstOrDefault (x => x.Match.Success); + + if (info == null) + continue; + + resources = info.Handler (info.Match, isApp, resources, resourceMap); + } + } + + return resources ?? new CodeTypeDeclaration ("Resource") { IsPartial = true }; + } + + static KeyValuePair, CodeTypeDeclaration>> Parse (string regex, Func, CodeTypeDeclaration> f) + { + return new KeyValuePair, CodeTypeDeclaration>> (new Regex (regex), f); + } + + // public finall class R { + // public static fnal class string|anim|styleable|etc. { + // public static final int field = 0xZZ; + // public static final int [] array = { + // 0xXX, 0xYY, 0xZZ + // } + // } + // } List, CodeTypeDeclaration>>> Parser; public JavaResourceParser (TaskLoggingHelper log) : base (log) @@ -88,33 +124,5 @@ public JavaResourceParser (TaskLoggingHelper log) : base (log) }), }; } - - public CodeTypeDeclaration Parse (string file, bool isApp, Dictionary resourceMap) - { - if (!File.Exists (file)) - throw new InvalidOperationException ("Specified Java resource file was not found: " + file); - - CodeTypeDeclaration? resources = null; - - using (var reader = File.OpenText (file)) { - string line; - - while ((line = reader.ReadLine ()) != null) { - var info = Parser.Select (p => new { Match = p.Key.Match (line), Handler = p.Value }).FirstOrDefault (x => x.Match.Success); - - if (info == null) - continue; - - resources = info.Handler (info.Match, isApp, resources, resourceMap); - } - } - - return resources ?? new CodeTypeDeclaration ("Resource") { IsPartial = true }; - } - - static KeyValuePair, CodeTypeDeclaration>> Parse (string regex, Func, CodeTypeDeclaration> f) - { - return new KeyValuePair, CodeTypeDeclaration>> (new Regex (regex), f); - } } } From 2179b04e327ea39a51c31f2409be77122bde98ee Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 30 Apr 2026 17:17:07 -0500 Subject: [PATCH 7/7] Address code review feedback - ConvertCustomView: guard resdir null check properly instead of using ?? "" which changes FixupResourceFilename behavior - AndroidError/AndroidWarning: use informative fallback message for missing resources instead of empty string - ManagedResourceParser: add missing blank line after constructor Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs | 3 ++- .../Tasks/AndroidWarning.cs | 3 ++- .../Tasks/ConvertCustomView.cs | 10 ++++++++-- .../Utilities/ManagedResourceParser.cs | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs index 811f8f454a7..92b2c79491e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidError.cs @@ -32,7 +32,8 @@ public override bool Execute () { Log.LogCodedError ( Code, - Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture) ?? "", + Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture) + ?? $"(Missing resource: {ResourceName})", FormatArguments ?? [] ); return false; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs index c38ebcc65fa..1585d2cfed1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidWarning.cs @@ -32,7 +32,8 @@ public override bool Execute () { Log.LogCodedWarning ( Code, - Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture) ?? "", + Properties.Resources.ResourceManager.GetString (ResourceName, Properties.Resources.Culture) + ?? $"(Missing resource: {ResourceName})", FormatArguments ?? [] ); return true; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs index d15621ca4f4..6f6bc56af9e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs @@ -56,10 +56,16 @@ public override bool RunTask () ITaskItem? resdir = ResourceDirectories?.FirstOrDefault (x => file.StartsWith (x.ItemSpec, StringComparison.OrdinalIgnoreCase)); switch (level) { case TraceLevel.Error: - Log.FixupResourceFilenameAndLogCodedError ("XA1002", message, file, resdir?.ItemSpec ?? "", resource_name_case_map); + if (resdir != null) + Log.FixupResourceFilenameAndLogCodedError ("XA1002", message, file, resdir.ItemSpec, resource_name_case_map); + else + Log.LogCodedError ("XA1002", file: file, lineNumber: 0, message: message); break; case TraceLevel.Warning: - Log.FixupResourceFilenameAndLogCodedError ("XA1001", message, file, resdir?.ItemSpec ?? "", resource_name_case_map); + if (resdir != null) + Log.FixupResourceFilenameAndLogCodedError ("XA1001", message, file, resdir.ItemSpec, resource_name_case_map); + else + Log.LogCodedWarning ("XA1001", file: file, lineNumber: 0, message: message); break; default: Log.LogDebugMessage (message); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs index e270004f214..d3da941273f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs @@ -20,6 +20,7 @@ namespace Xamarin.Android.Tasks class ManagedResourceParser : FileResourceParser { public ManagedResourceParser (TaskLoggingHelper log) : base (log) { } + class CompareTuple : IComparer<(int Key, CodeMemberField Value)> { public int Compare((int Key, CodeMemberField Value) x, (int Key, CodeMemberField Value) y)