Skip to content

Commit

Permalink
.net8 Android not building because Resources from resx not found.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dellis1972 committed Jul 1, 2024
1 parent 06bb1dc commit b8ccd51
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 15 deletions.
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 @@ -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
Expand Down
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)' == ''">internal</AndroidResourceDesignerClassModifier>
</PropertyGroup>
<Message Text="_OuterIntermediateOutputPath: $(_OuterIntermediateOutputPath)" />
<Message Text="IntermediateOutputPath: $(IntermediateOutputPath)" />
Expand Down Expand Up @@ -103,6 +104,7 @@ Copyright (C) 2016 Xamarin. All rights reserved.
<GenerateResourceDesignerIntermediateClass
IsApplication="$(AndroidApplication)"
Namespace="$(AndroidResgenNamespace)"
Modifier="$(AndroidResourceDesignerClassModifier)"
OutputFile="$(_GenerateResourceDesignerClassFile)"
>
</GenerateResourceDesignerIntermediateClass>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace %NAMESPACE% {
/// Android Resource Designer class.
/// Exposes the Android Resource designer assembly into the project Namespace.
/// </summary>
public partial class Resource : %BASECLASS% {
%MODIFIER% partial class Resource : %BASECLASS% {
}
#pragma warning restore IDE0002
}
Expand All @@ -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;
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", "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,
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 @@ -558,7 +558,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 b8ccd51

Please sign in to comment.