Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Don't remove resx Resource.designer.cs (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
dellis1972 authored Oct 15, 2024
1 parent 1857f28 commit 15a1429
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 21 deletions.
2 changes: 2 additions & 0 deletions Documentation/docs-mobile/TOC.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@
href: messages/xa1036.md
- name: XA1037
href: messages/xa1037.md
- name: XA1038
href: messages/xa1038.md
- name: "XA2xxx: Linker"
items:
- name: "XA2xxx: Linker"
Expand Down
9 changes: 9 additions & 0 deletions Documentation/docs-mobile/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,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` and `internal`.

By default this will be `public`.

Added in .NET 9.

## AndroidSdkBuildToolsVersion

The Android SDK
Expand Down
1 change: 1 addition & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ or 'Help->Report a Problem' in Visual Studio for Mac.
+ [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).
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).
+ [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.
+ [XA1038](xa1038.md): The '{0}' MSBuild property has an invalid value. Value values are {1}.

## XA2xxx: Linker

Expand Down
21 changes: 21 additions & 0 deletions Documentation/docs-mobile/messages/xa1038.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: .NET for Android error XA1038
description: XA1038 error code
ms.date: 06/27/2024
---
# .NET for Android error XA1038

## Example messages

```
The 'AndroidResourceDesignerClassModifier' MSBuild property has an invalid value of 'foo'. A valid value is one of: 'public', 'internal'.
```

## Solution

Edit your csproj directly and remove the referenced MSBuild property.

Test your project to ensure the new behavior is functionally equivalent.

If not, file an [issue](https://github.com/xamarin/xamarin-android/issues) so a
solution can be found before the deprecated flag is removed.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Copyright (C) 2016 Xamarin. All rights reserved.
<_GenerateResourceDesignerClassFile Condition=" '$(Language)' == 'F#' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).fs</_GenerateResourceDesignerClassFile>
<_GenerateResourceDesignerClassFile Condition=" '$(_GenerateResourceDesignerClassFile)' == '' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).cs</_GenerateResourceDesignerClassFile>
<_GenerateResourceCaseMapFile>$(_DesignerIntermediateOutputPath)case_map.txt</_GenerateResourceCaseMapFile>
<AndroidResourceDesignerClassModifier Condition=" '$(AndroidResourceDesignerClassModifier)' == '' ">public</AndroidResourceDesignerClassModifier>
</PropertyGroup>
<Message Text="_OuterIntermediateOutputPath: $(_OuterIntermediateOutputPath)" />
<Message Text="IntermediateOutputPath: $(IntermediateOutputPath)" />
Expand Down Expand Up @@ -100,9 +101,18 @@ Copyright (C) 2016 Xamarin. All rights reserved.
<!-- Creates a Resource.designer.cs file in the Intermediate output path which derives from the
Designer Assembly, for backward compatability.
-->
<AndroidError Code="XA1038"
ResourceName="XA1038"
FormatArguments="AndroidResourceDesignerClassModifier;$(AndroidResourceDesignerClassModifier);&apos;public&apos;, &apos;internal&apos;"
Condition="
'$([System.String]::CompareOrdinal(&quot;public&quot;, $(AndroidResourceDesignerClassModifier)))' != '0' And
'$([System.String]::CompareOrdinal(&quot;internal&quot;, $(AndroidResourceDesignerClassModifier)))' != '0'
"
/>
<GenerateResourceDesignerIntermediateClass
IsApplication="$(AndroidApplication)"
Namespace="$(AndroidResgenNamespace)"
Modifier="$(AndroidResourceDesignerClassModifier)"
OutputFile="$(_GenerateResourceDesignerClassFile)"
>
</GenerateResourceDesignerIntermediateClass>
Expand Down
7 changes: 7 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,13 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS
<comment>The following are literal names and should not be translated: MSBuild, .NET.
{0} - The deprecated MSBuild property name
{1} - The numeric version of .NET</comment>
</data>
<data name="XA1038" xml:space="preserve">
<value>The '{0}' MSBuild property has an invalid value of '{1}'. A valid value is one of: {2}.</value>
<comment>The following are literal names and should not be translated: MSBuild.
{0} - The MSBuild property name which has invalid values.
{1} - The current value of the property
{2} - The list of valid values for the property these can be Comma-separated.</comment>
</data>
<data name="XA1039" xml:space="preserve">
<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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ namespace %NAMESPACE% {
/// Android Resource Designer class.
/// Exposes the Android Resource designer assembly into the project Namespace.
/// </summary>
[GeneratedCode(""%TOOL%"", ""%VERSION%"")]
public partial class Resource : %BASECLASS% {
%MODIFIER% partial class Resource : %BASECLASS% {
}
#pragma warning restore IDE0002
}
Expand All @@ -42,16 +41,16 @@ public partial class Resource : %BASECLASS% {
//------------------------------------------------------------------------------
namespace %NAMESPACE%
[<type:System.CodeDom.Compiler.GeneratedCode(""%TOOL%"", ""%VERSION%"")>]
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;
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);
Expand All @@ -61,14 +60,12 @@ public override bool RunTask ()
string template = "";
if (isCSharp) {
template = CSharpTemplate.Replace ("%NAMESPACE%", Namespace)
.Replace ("%BASECLASS%", ns)
.Replace ("%VERSION%", version.ToString ())
.Replace ("%TOOL%", nameof (GenerateResourceDesignerIntermediateClass));
.Replace ("%BASECLASS%", baseClass)
.Replace ("%MODIFIER%", Modifier.ToLower ());
} else if (isFSharp) {
template = FSharpTemplate.Replace ("%NAMESPACE%", Namespace)
.Replace ("%BASECLASS%", ns)
.Replace ("%VERSION%", version.ToString ())
.Replace ("%TOOL%", nameof (GenerateResourceDesignerIntermediateClass));
.Replace ("%BASECLASS%", baseClass)
.Replace ("%MODIFIER%", Modifier.ToLower ());
}

Files.CopyIfStringChanged (template, OutputFile.ItemSpec);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ("<data name=\"CancelButton\"><value>Cancel</value></data>")
new BuildItem ("EmbeddedResource", "Resource.resx") {
TextContent = () => InlineData.ResxWithContents ("<data name=\"CancelButton\"><value>Cancel</value></data>"),
},
new BuildItem ("EmbeddedResource", "Foo.es.resx") {
new BuildItem ("EmbeddedResource", "Resource.es.resx") {
TextContent = () => InlineData.ResxWithContents ("<data name=\"CancelButton\"><value>Cancelar</value></data>")
},
new AndroidItem.TransformFile ("Transforms.xml") {
Expand All @@ -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") {
Expand All @@ -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<string>)) {
Update = () => "Resource.resx",
TextContent = () => InlineData.ResxWithContents ("<data name=\"CancelButton\"><value>Cancel</value></data>"),
Metadata = {
{ "Generator", "ResXFileCodeGenerator" },
{ "LastGenOutput", "Resource.designer.cs" }
},
});
proj.OtherBuildItems.Add (new BuildItem ("Compile", default (Func<string>)) {
TextContent = () => InlineData.DesignerWithContents (proj.ProjectName, "Resource", "public 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -97,15 +97,16 @@ 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 ());
}

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)
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public static string ToXml (IShortFormProject project)
if (project.OtherBuildItems.Count > 0) {
sb.AppendLine ("\t<ItemGroup>");
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<ItemGroup>
<CorrectCasedItem Include="%(Compile.Identity)" Condition=" '$(AndroidUseIntermediateDesignerFile)' == 'true' And '$(AndroidResgenFile)' != '' And '%(Compile.Identity)' == '$(AndroidResgenFile)'"/>
<!-- Forcably Remove the old Resource.designer.cs even if the AndroidResgenFile is not set.-->
<CorrectCasedItem Include="%(Compile.Identity)" Condition=" '$(AndroidUseDesignerAssembly)' == 'True' And '%(Compile.Filename)%(Compile.Extension)' == '$(_AndroidResourceDesigner)' "/>
<CorrectCasedItem Include="%(Compile.Identity)" Condition=" '$(AndroidUseDesignerAssembly)' == 'True' And '%(Compile.Filename)%(Compile.Extension)' == '$(_AndroidResourceDesigner)' And '%(Compile.DependentUpon)' == '' "/>
<Compile Remove="@(CorrectCasedItem)" />
<Compile Include="$(_AndroidResourceDesignerFile)" Condition=" '$(AndroidUseIntermediateDesignerFile)' == 'true' And '$(AndroidUseDesignerAssembly)' != 'true' " />
</ItemGroup>
Expand Down

0 comments on commit 15a1429

Please sign in to comment.