From b8ccd513660658812497dd5f997c402f07d0a56e Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 26 Jun 2024 09:28:59 +0100 Subject: [PATCH] .net8 Android not building because Resources from resx not found. Fixes https://github.com/dotnet/maui/issues/19117 With the release of the new Resource Designer assembly we purposely removed the existing legacy `Resource.designer.cs` file from the `@(Compile)` ItemGroup. This is so that we did not get any clashes with the new system. Unfortunately the name `Resource.designer.cs` is also used by developers as the name for the generated class when using `.resx` files. As a result we got the above bug report because the class was disappearing. ``` error CS0234: The type or namespace name 'Resources' does not exist in the namespace 'StringResourcesBug' (are you missing an assembly reference?) ``` It turns out that the `_RemoveLegacyDesigner` was being a bit too aggressive with its removal of files. Because it was matching on filename only it was removing files which had nothing to do with Android Resources. This would cause the above error. The fix in this case is to check for additional metadata. In the case of `.resx` file designers they typically have a `DependentUpon` metadata to signal that they are generated from the `.resx`. So we should check this metadata to make sure it is NOT set before removing a file. A new unit test was added to test this fix and it worked. But it did show up one other issue. In certain cases we would end up with two `Resource` classes in the same namespace. This would then cause the following build error. ``` error CS0260: Missing partial modifier on declaration of type 'Resource'; another partial declaration of this type exists ``` This is because the two classes had different modifiers. We could also get an error if the access modifiers are different (public vs internal). So lets add a new MSBuild property `AndroidResourceDesignerClassModifier`. This property defaults to `public`, but can be overridden by the user to control the access modifier of the generated class. --- .../building-apps/build-properties.md | 9 +++++++ .../Xamarin.Android.Resource.Designer.targets | 2 ++ ...nerateResourceDesignerIntermediateClass.cs | 13 ++++++---- .../Xamarin.Android.Build.Tests/BuildTest.cs | 24 +++++++++++++++---- .../Utilities/InlineData.cs | 9 +++---- .../Utilities/XmlUtils.cs | 3 ++- .../Xamarin.Android.Common.targets | 2 +- 7 files changed, 47 insertions(+), 15 deletions(-) diff --git a/Documentation/docs-mobile/building-apps/build-properties.md b/Documentation/docs-mobile/building-apps/build-properties.md index 0c28c3efd43..5e5457881c1 100644 --- a/Documentation/docs-mobile/building-apps/build-properties.md +++ b/Documentation/docs-mobile/building-apps/build-properties.md @@ -1079,6 +1079,15 @@ Specifies the name of the Resource file to generate. The default template sets this to `Resource.designer.cs`. +## AndroidResourceDesignerClassModifier + +Specifies the class modifier for the intermediate `Resource` class which is +generated. Valid values are `public`, `internal` and `private`. + +By default this will be `public`. + +Added in .NET 9. + ## AndroidSdkBuildToolsVersion The Android SDK diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets index 389d1857e65..9b26dbcb518 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets @@ -50,6 +50,7 @@ Copyright (C) 2016 Xamarin. All rights reserved. <_GenerateResourceDesignerClassFile Condition=" '$(Language)' == 'F#' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).fs <_GenerateResourceDesignerClassFile Condition=" '$(_GenerateResourceDesignerClassFile)' == '' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).cs <_GenerateResourceCaseMapFile>$(_DesignerIntermediateOutputPath)case_map.txt + internal @@ -103,6 +104,7 @@ Copyright (C) 2016 Xamarin. All rights reserved. diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs index 22f9d4cf338..d5bf98aeeaa 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs @@ -28,7 +28,7 @@ namespace %NAMESPACE% { /// Android Resource Designer class. /// Exposes the Android Resource designer assembly into the project Namespace. /// - public partial class Resource : %BASECLASS% { + %MODIFIER% partial class Resource : %BASECLASS% { } #pragma warning restore IDE0002 } @@ -40,25 +40,28 @@ public partial class Resource : %BASECLASS% { //------------------------------------------------------------------------------ namespace %NAMESPACE% -type Resource = %BASECLASS% +type %MODIFIER% Resource = %BASECLASS% "; public string Namespace { get; set; } + public string Modifier { get; set; } = "public"; public bool IsApplication { get; set; } = false; public ITaskItem OutputFile { get; set; } public override bool RunTask () { - string ns = IsApplication ? ResourceDesignerConstants : ResourceDesigner; + string baseClass = IsApplication ? ResourceDesignerConstants : ResourceDesigner; + string modifier = IsApplication ? Modifier : "public"; var extension = Path.GetExtension (OutputFile.ItemSpec); var language = string.Compare (extension, ".fs", StringComparison.OrdinalIgnoreCase) == 0 ? "F#" : CodeDomProvider.GetLanguageFromExtension (extension); //bool isVB = string.Equals (extension, ".vb", StringComparison.OrdinalIgnoreCase); bool isFSharp = string.Equals (language, "F#", StringComparison.OrdinalIgnoreCase); bool isCSharp = string.Equals (language, "C#", StringComparison.OrdinalIgnoreCase); + bool isVB = string.Equals (language, "vb", StringComparison.OrdinalIgnoreCase); string template = ""; if (isCSharp) - template = CSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", ns); + template = CSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", baseClass).Replace ("%MODIFIER%", modifier); else if (isFSharp) - template = FSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", ns); + template = FSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", baseClass).Replace ("%MODIFIER%", modifier); Files.CopyIfStringChanged (template, OutputFile.ItemSpec); return !Log.HasLoggedErrors; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs index e42436ada2d..a9eb9cd6fd1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs @@ -44,10 +44,10 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo new Package { Id = "System.Text.Json", Version = "8.0.*" }, }, Sources = { - new BuildItem ("EmbeddedResource", "Foo.resx") { - TextContent = () => InlineData.ResxWithContents ("Cancel") + new BuildItem ("EmbeddedResource", "Resource.resx") { + TextContent = () => InlineData.ResxWithContents ("Cancel"), }, - new BuildItem ("EmbeddedResource", "Foo.es.resx") { + new BuildItem ("EmbeddedResource", "Resource.es.resx") { TextContent = () => InlineData.ResxWithContents ("Cancelar") }, new AndroidItem.TransformFile ("Transforms.xml") { @@ -61,7 +61,8 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo }, } }; - proj.MainActivity = proj.DefaultMainActivity.Replace (": Activity", ": AndroidX.AppCompat.App.AppCompatActivity"); + proj.MainActivity = proj.DefaultMainActivity.Replace (": Activity", ": AndroidX.AppCompat.App.AppCompatActivity") + .Replace ("//${AFTER_ONCREATE}", @"button.Text = Resource.CancelButton;"); proj.SetProperty ("AndroidUseAssemblyStore", usesAssemblyStore.ToString ()); proj.SetProperty ("RunAOTCompilation", aot.ToString ()); proj.OtherBuildItems.Add (new AndroidItem.InputJar ("javaclasses.jar") { @@ -70,6 +71,21 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo proj.OtherBuildItems.Add (new BuildItem ("JavaSourceJar", "javaclasses-sources.jar") { BinaryContent = () => ResourceData.JavaSourceJarTestSourcesJar, }); + proj.OtherBuildItems.Add (new BuildItem ("EmbeddedResource", default (Func)) { + Update = () => "Resource.resx", + TextContent = () => InlineData.ResxWithContents ("Cancel"), + Metadata = { + { "Generator", "ResXFileCodeGenerator" }, + { "LastGenOutput", "Resource.designer.cs" } + }, + }); + proj.OtherBuildItems.Add (new BuildItem ("Compile", default (Func)) { + TextContent = () => InlineData.DesignerWithContents (proj.ProjectName, "Resource", "internal partial", new string[] {"CancelButton"}), + Update = () => "Resource.designer.cs", + Metadata = { + { "DependentUpon", "Resource.resx" }, + }, + }); proj.AndroidJavaSources.Add (new AndroidItem.AndroidJavaSource ("JavaSourceTestExtension.java") { Encoding = Encoding.ASCII, TextContent = () => ResourceData.JavaSourceTestExtension, diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/InlineData.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/InlineData.cs index b9b49d4d5a4..7b53d675680 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/InlineData.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/InlineData.cs @@ -37,7 +37,7 @@ namespace @projectName@ [System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")] [System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class @className@ { + @modifier@ class @className@ { private static System.Resources.ResourceManager resourceMan; @@ -87,7 +87,7 @@ public static void AddCultureResourcesToProject (IShortFormProject proj, string } } - public static string DesignerWithContents (string projectName, string className, string[] dataNames) + public static string DesignerWithContents (string projectName, string className, string modifier, string[] dataNames) { var content = new StringBuilder (); foreach (string data in dataNames) { @@ -97,7 +97,8 @@ public static string DesignerWithContents (string projectName, string className, }} }}" + Environment.NewLine, data); } - return Designer.Replace ("@className@", className) + return Designer.Replace ("@modifier@", modifier) + .Replace ("@className@", className) .Replace ("@projectName@", projectName) .Replace ("@content@", content.ToString ()); } @@ -105,7 +106,7 @@ public static string DesignerWithContents (string projectName, string className, public static void AddCultureResourceDesignerToProject (IShortFormProject proj, string projectName, string className, params string[] dataNames) { proj.OtherBuildItems.Add (new BuildItem.Source ($"{className}.Designer.cs") { - TextContent = () => InlineData.DesignerWithContents (projectName, className, dataNames) + TextContent = () => InlineData.DesignerWithContents (projectName, className, "internal", dataNames) }); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/XmlUtils.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/XmlUtils.cs index f73aa9ffa5e..c051a841dfa 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/XmlUtils.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/XmlUtils.cs @@ -41,7 +41,8 @@ public static string ToXml (IShortFormProject project) if (project.OtherBuildItems.Count > 0) { sb.AppendLine ("\t"); foreach (var bi in project.OtherBuildItems) { - if (bi.BuildAction != BuildActions.EmbeddedResource) { + // If its an EmbeddedResource ignore it, unless it has an Update method set. + if (bi.BuildAction != BuildActions.EmbeddedResource || bi.Update != null) { AppendBuildItem (sb, bi); } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index a919417c349..d7c80e37ffc 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -558,7 +558,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - +