Skip to content

Commit 15a1429

Browse files
authored
[Xamarin.Android.Build.Tasks] Don't remove resx Resource.designer.cs (#9057)
Fixes: #9163 Context: dc3ccf2 Context: 3ec1b15 With the release of the new Resource Designer assembly (dc3ccf2) 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 following CS0234 error 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 CS0234 error. The fix in this case is to check for additional metadata. In the case of `.resx` file designers they typically have `%(DependentUpon)` metadata to signal that they are generated from the `.resx`. We should check `%(DependentUpon)` to make sure it is NOT set before removing a file from the `@(Compile)` item group. 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`). 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. Also we have to remove the `[GeneratedCode]` Attribute usage on the generated `Resource` class, as it can conflict with the one that is auto added to the `Resource.designer.cs` generated by the `resx` code generator. If we leave this in place we get the following error. __Microsoft.Android.Resource.Designer.cs(15,3,15,16): error CS0579: Duplicate 'GeneratedCode' attribute Unfortunately [`GeneratedCodeAttribute`][1] is marked as `AllowMultiple=false` and in this case there is no way to know if it exists on the class already as its a `partial` class. [1]: https://learn.microsoft.com/en-us/dotnet/api/system.codedom.compiler.generatedcodeattribute?view=net-8.0
1 parent 1857f28 commit 15a1429

File tree

11 files changed

+86
-21
lines changed

11 files changed

+86
-21
lines changed

Documentation/docs-mobile/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@
240240
href: messages/xa1036.md
241241
- name: XA1037
242242
href: messages/xa1037.md
243+
- name: XA1038
244+
href: messages/xa1038.md
243245
- name: "XA2xxx: Linker"
244246
items:
245247
- name: "XA2xxx: Linker"

Documentation/docs-mobile/building-apps/build-properties.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,15 @@ Specifies the name of the Resource
11001100
file to generate. The default template sets this to
11011101
`Resource.designer.cs`.
11021102

1103+
## AndroidResourceDesignerClassModifier
1104+
1105+
Specifies the class modifier for the intermediate `Resource` class which is
1106+
generated. Valid values are `public` and `internal`.
1107+
1108+
By default this will be `public`.
1109+
1110+
Added in .NET 9.
1111+
11031112
## AndroidSdkBuildToolsVersion
11041113

11051114
The Android SDK

Documentation/docs-mobile/messages/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ or 'Help->Report a Problem' in Visual Studio for Mac.
144144
+ [XA1036](xa1036.md): AndroidManifest.xml //uses-sdk/@android:minSdkVersion '29' does not match the $(SupportedOSPlatformVersion) value '21' in the project file (if there is no $(SupportedOSPlatformVersion) value in the project file, then a default value has been assumed).
145145
Either change the value in the AndroidManifest.xml to match the $(SupportedOSPlatformVersion) value, or remove the value in the AndroidManifest.xml (and add a $(SupportedOSPlatformVersion) value to the project file if it doesn't already exist).
146146
+ [XA1037](xa1037.md): The '{0}' MSBuild property is deprecated and will be removed in .NET {1}. See https://aka.ms/net-android-deprecations for more details.
147+
+ [XA1038](xa1038.md): The '{0}' MSBuild property has an invalid value. Value values are {1}.
147148

148149
## XA2xxx: Linker
149150

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: .NET for Android error XA1038
3+
description: XA1038 error code
4+
ms.date: 06/27/2024
5+
---
6+
# .NET for Android error XA1038
7+
8+
## Example messages
9+
10+
```
11+
The 'AndroidResourceDesignerClassModifier' MSBuild property has an invalid value of 'foo'. A valid value is one of: 'public', 'internal'.
12+
```
13+
14+
## Solution
15+
16+
Edit your csproj directly and remove the referenced MSBuild property.
17+
18+
Test your project to ensure the new behavior is functionally equivalent.
19+
20+
If not, file an [issue](https://github.com/xamarin/xamarin-android/issues) so a
21+
solution can be found before the deprecated flag is removed.

src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Copyright (C) 2016 Xamarin. All rights reserved.
5050
<_GenerateResourceDesignerClassFile Condition=" '$(Language)' == 'F#' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).fs</_GenerateResourceDesignerClassFile>
5151
<_GenerateResourceDesignerClassFile Condition=" '$(_GenerateResourceDesignerClassFile)' == '' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).cs</_GenerateResourceDesignerClassFile>
5252
<_GenerateResourceCaseMapFile>$(_DesignerIntermediateOutputPath)case_map.txt</_GenerateResourceCaseMapFile>
53+
<AndroidResourceDesignerClassModifier Condition=" '$(AndroidResourceDesignerClassModifier)' == '' ">public</AndroidResourceDesignerClassModifier>
5354
</PropertyGroup>
5455
<Message Text="_OuterIntermediateOutputPath: $(_OuterIntermediateOutputPath)" />
5556
<Message Text="IntermediateOutputPath: $(IntermediateOutputPath)" />
@@ -100,9 +101,18 @@ Copyright (C) 2016 Xamarin. All rights reserved.
100101
<!-- Creates a Resource.designer.cs file in the Intermediate output path which derives from the
101102
Designer Assembly, for backward compatability.
102103
-->
104+
<AndroidError Code="XA1038"
105+
ResourceName="XA1038"
106+
FormatArguments="AndroidResourceDesignerClassModifier;$(AndroidResourceDesignerClassModifier);&apos;public&apos;, &apos;internal&apos;"
107+
Condition="
108+
'$([System.String]::CompareOrdinal(&quot;public&quot;, $(AndroidResourceDesignerClassModifier)))' != '0' And
109+
'$([System.String]::CompareOrdinal(&quot;internal&quot;, $(AndroidResourceDesignerClassModifier)))' != '0'
110+
"
111+
/>
103112
<GenerateResourceDesignerIntermediateClass
104113
IsApplication="$(AndroidApplication)"
105114
Namespace="$(AndroidResgenNamespace)"
115+
Modifier="$(AndroidResourceDesignerClassModifier)"
106116
OutputFile="$(_GenerateResourceDesignerClassFile)"
107117
>
108118
</GenerateResourceDesignerIntermediateClass>

src/Xamarin.Android.Build.Tasks/Properties/Resources.resx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,13 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS
10131013
<comment>The following are literal names and should not be translated: MSBuild, .NET.
10141014
{0} - The deprecated MSBuild property name
10151015
{1} - The numeric version of .NET</comment>
1016+
</data>
1017+
<data name="XA1038" xml:space="preserve">
1018+
<value>The '{0}' MSBuild property has an invalid value of '{1}'. A valid value is one of: {2}.</value>
1019+
<comment>The following are literal names and should not be translated: MSBuild.
1020+
{0} - The MSBuild property name which has invalid values.
1021+
{1} - The current value of the property
1022+
{2} - The list of valid values for the property these can be Comma-separated.</comment>
10161023
</data>
10171024
<data name="XA1039" xml:space="preserve">
10181025
<value>The Android Support libraries are not supported in .NET 9 and later, please migrate to AndroidX. See https://aka.ms/net-android/androidx for more details.</value>

src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ namespace %NAMESPACE% {
2929
/// Android Resource Designer class.
3030
/// Exposes the Android Resource designer assembly into the project Namespace.
3131
/// </summary>
32-
[GeneratedCode(""%TOOL%"", ""%VERSION%"")]
33-
public partial class Resource : %BASECLASS% {
32+
%MODIFIER% partial class Resource : %BASECLASS% {
3433
}
3534
#pragma warning restore IDE0002
3635
}
@@ -42,16 +41,16 @@ public partial class Resource : %BASECLASS% {
4241
//------------------------------------------------------------------------------
4342
namespace %NAMESPACE%
4443
45-
[<type:System.CodeDom.Compiler.GeneratedCode(""%TOOL%"", ""%VERSION%"")>]
46-
type Resource = %BASECLASS%
44+
type %MODIFIER% Resource = %BASECLASS%
4745
";
4846

4947
public string Namespace { get; set; }
48+
public string Modifier { get; set; } = "public";
5049
public bool IsApplication { get; set; } = false;
5150
public ITaskItem OutputFile { get; set; }
5251
public override bool RunTask ()
5352
{
54-
string ns = IsApplication ? ResourceDesignerConstants : ResourceDesigner;
53+
string baseClass = IsApplication ? ResourceDesignerConstants : ResourceDesigner;
5554
var extension = Path.GetExtension (OutputFile.ItemSpec);
5655
var language = string.Compare (extension, ".fs", StringComparison.OrdinalIgnoreCase) == 0 ? "F#" : CodeDomProvider.GetLanguageFromExtension (extension);
5756
//bool isVB = string.Equals (extension, ".vb", StringComparison.OrdinalIgnoreCase);
@@ -61,14 +60,12 @@ public override bool RunTask ()
6160
string template = "";
6261
if (isCSharp) {
6362
template = CSharpTemplate.Replace ("%NAMESPACE%", Namespace)
64-
.Replace ("%BASECLASS%", ns)
65-
.Replace ("%VERSION%", version.ToString ())
66-
.Replace ("%TOOL%", nameof (GenerateResourceDesignerIntermediateClass));
63+
.Replace ("%BASECLASS%", baseClass)
64+
.Replace ("%MODIFIER%", Modifier.ToLower ());
6765
} else if (isFSharp) {
6866
template = FSharpTemplate.Replace ("%NAMESPACE%", Namespace)
69-
.Replace ("%BASECLASS%", ns)
70-
.Replace ("%VERSION%", version.ToString ())
71-
.Replace ("%TOOL%", nameof (GenerateResourceDesignerIntermediateClass));
67+
.Replace ("%BASECLASS%", baseClass)
68+
.Replace ("%MODIFIER%", Modifier.ToLower ());
7269
}
7370

7471
Files.CopyIfStringChanged (template, OutputFile.ItemSpec);

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo
4444
new Package { Id = "System.Text.Json", Version = "8.0.*" },
4545
},
4646
Sources = {
47-
new BuildItem ("EmbeddedResource", "Foo.resx") {
48-
TextContent = () => InlineData.ResxWithContents ("<data name=\"CancelButton\"><value>Cancel</value></data>")
47+
new BuildItem ("EmbeddedResource", "Resource.resx") {
48+
TextContent = () => InlineData.ResxWithContents ("<data name=\"CancelButton\"><value>Cancel</value></data>"),
4949
},
50-
new BuildItem ("EmbeddedResource", "Foo.es.resx") {
50+
new BuildItem ("EmbeddedResource", "Resource.es.resx") {
5151
TextContent = () => InlineData.ResxWithContents ("<data name=\"CancelButton\"><value>Cancelar</value></data>")
5252
},
5353
new AndroidItem.TransformFile ("Transforms.xml") {
@@ -61,7 +61,8 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo
6161
},
6262
}
6363
};
64-
proj.MainActivity = proj.DefaultMainActivity.Replace (": Activity", ": AndroidX.AppCompat.App.AppCompatActivity");
64+
proj.MainActivity = proj.DefaultMainActivity.Replace (": Activity", ": AndroidX.AppCompat.App.AppCompatActivity")
65+
.Replace ("//${AFTER_ONCREATE}", @"button.Text = Resource.CancelButton;");
6566
proj.SetProperty ("AndroidUseAssemblyStore", usesAssemblyStore.ToString ());
6667
proj.SetProperty ("RunAOTCompilation", aot.ToString ());
6768
proj.OtherBuildItems.Add (new AndroidItem.InputJar ("javaclasses.jar") {
@@ -70,6 +71,21 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo
7071
proj.OtherBuildItems.Add (new BuildItem ("JavaSourceJar", "javaclasses-sources.jar") {
7172
BinaryContent = () => ResourceData.JavaSourceJarTestSourcesJar,
7273
});
74+
proj.OtherBuildItems.Add (new BuildItem ("EmbeddedResource", default (Func<string>)) {
75+
Update = () => "Resource.resx",
76+
TextContent = () => InlineData.ResxWithContents ("<data name=\"CancelButton\"><value>Cancel</value></data>"),
77+
Metadata = {
78+
{ "Generator", "ResXFileCodeGenerator" },
79+
{ "LastGenOutput", "Resource.designer.cs" }
80+
},
81+
});
82+
proj.OtherBuildItems.Add (new BuildItem ("Compile", default (Func<string>)) {
83+
TextContent = () => InlineData.DesignerWithContents (proj.ProjectName, "Resource", "public partial", new string[] {"CancelButton"}),
84+
Update = () => "Resource.designer.cs",
85+
Metadata = {
86+
{ "DependentUpon", "Resource.resx" },
87+
},
88+
});
7389
proj.AndroidJavaSources.Add (new AndroidItem.AndroidJavaSource ("JavaSourceTestExtension.java") {
7490
Encoding = Encoding.ASCII,
7591
TextContent = () => ResourceData.JavaSourceTestExtension,

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/InlineData.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ namespace @projectName@
3737
[System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")]
3838
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
3939
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
40-
internal class @className@ {
40+
@modifier@ class @className@ {
4141
4242
private static System.Resources.ResourceManager resourceMan;
4343
@@ -87,7 +87,7 @@ public static void AddCultureResourcesToProject (IShortFormProject proj, string
8787
}
8888
}
8989

90-
public static string DesignerWithContents (string projectName, string className, string[] dataNames)
90+
public static string DesignerWithContents (string projectName, string className, string modifier, string[] dataNames)
9191
{
9292
var content = new StringBuilder ();
9393
foreach (string data in dataNames) {
@@ -97,15 +97,16 @@ public static string DesignerWithContents (string projectName, string className,
9797
}}
9898
}}" + Environment.NewLine, data);
9999
}
100-
return Designer.Replace ("@className@", className)
100+
return Designer.Replace ("@modifier@", modifier)
101+
.Replace ("@className@", className)
101102
.Replace ("@projectName@", projectName)
102103
.Replace ("@content@", content.ToString ());
103104
}
104105

105106
public static void AddCultureResourceDesignerToProject (IShortFormProject proj, string projectName, string className, params string[] dataNames)
106107
{
107108
proj.OtherBuildItems.Add (new BuildItem.Source ($"{className}.Designer.cs") {
108-
TextContent = () => InlineData.DesignerWithContents (projectName, className, dataNames)
109+
TextContent = () => InlineData.DesignerWithContents (projectName, className, "internal", dataNames)
109110
});
110111
}
111112
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/XmlUtils.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public static string ToXml (IShortFormProject project)
4141
if (project.OtherBuildItems.Count > 0) {
4242
sb.AppendLine ("\t<ItemGroup>");
4343
foreach (var bi in project.OtherBuildItems) {
44-
if (bi.BuildAction != BuildActions.EmbeddedResource) {
44+
// If its an EmbeddedResource ignore it, unless it has an Update method set.
45+
if (bi.BuildAction != BuildActions.EmbeddedResource || bi.Update != null) {
4546
AppendBuildItem (sb, bi);
4647
}
4748
}

0 commit comments

Comments
 (0)