Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] _Microsoft.Android.Resource.Designer na…
Browse files Browse the repository at this point in the history
…mespace (#7681)

Context: dc3ccf2
Context: dotnet/maui#12520 (comment)
Context: https://discord.com/channels/732297728826277939/732297837953679412/1062137297438519296
Context: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0002

Consider this C# fragment:

	namespace Android.Content {
	    public class Intent {        
	    }
	}

	namespace Microsoft.Maui.Example {
	    public class Helper {
	        public static void UseIntent (Android.Content.Intent intent) {}
	    }
	}

Given a C# *namespace_or_type_name* such as `Android.Content.Intent`,
[ECMA 334 §7.8.1][0] will be used to determine what the type refers
to, resolving it to the Assembly-Qualified Name
`Android.Content.Intent, Mono.Android`.

All is good.

However, once dc3ccf2 enters the picture, we now have:

	// via dc3ccf2 & _Microsoft.Android.Resource.Designer.dll
	namespace Microsoft.Android.Resource.Designer {
	    public class Resource {
	    }
	}

	namespace Android.Content {
	    public class Intent {        
	    }
	}

	namespace Microsoft.Maui.Example {
	    public class Helper {
	        // CS0234 on the following line:		
	        public static void UseIntent (Android.Content.Intent intent) {}
	    }
	}

…and it fails to build with [error CS0234][1]:

	error CS0234: The type or namespace name 'Content' does not exist in the namespace 'Microsoft.Android' (are you missing an assembly reference?)

This only happens if the usage of `Android.Content.Intent` happens
*within* a `Microsoft.`-prefixed namespace.  Which is most of MAUI.

The cause of the error is that while attempting to resolve
`Android.Content.Intent`, search starts from the *current* namespace
`Microsoft.Maui.Example`, and the compiler will attempt to resolve
the types:

  * `Microsoft.Maui.Example.Android.Content.Intent`
    (because we're in the `Microsoft.Maui.Example` namespace.)
  * `Microsoft.Maui.Android.Content.Intent`
    (because `Microsoft.Maui` is a parent namespace)
  * `Microsoft.Android.Content.Intent`
    (because `Microsoft` is a parent parent namespace)

These are the *only* types that are resolved, and none of those exist.
This results in the CS0234 error.

There are two workarounds that the "offending" code can employ:

 1. Use `global`:

        namespace Microsoft.Maui.Example {
            public class Helper {
                public static void UseIntent (global::Android.Content.Intent intent) {}
            }
        }

 2. Add a `using Android.Content` and use `Intent`

        using Android.Content;
        namespace Microsoft.Maui.Example {
            public class Helper {
                public static void UseIntent (Intent intent) {}
            }
        }

Both of these require changing things *outside* of xamarin-android.

Rephrasing & simplifying: commit dc3ccf2 constitutes an *API break*,
as code which *previously* compiled *no longer compiles*.

Fix this by updating the `<GenerateResourceDesignerAssembly/>` task to
emit types into the `_Microsoft.Android.Resource.Designer` namespace;
note `_` prefix.  No One™ should have their code in a `_Microsoft.*`
namespace, so this shouldn't break anybody.  Additionally, update
`_Microsoft.Android.Resource.Designer.dll` so that
[`EditorBrowsableAttribute`][2] is placed on all the types, a'la:

	namespace _Microsoft.Android.Resource.Designer;
	[EditorBrowsable (EditorBrowsableState.Never)]
	public partial class Resource {
	}

	// In App project builds
	[EditorBrowsable (EditorBrowsableState.Never)]
	internal partial class ResourceConstant {
	}

Finally, update the Source Compatibility types so that the `Resource`
type disables the IDE0002 warning:

	namespace %CompatibilityNamespace% {
	    #pragma warning disable IDE0002
	    public partial class Resource : _Microsoft.Android.Resource.Designer.Resource {
	    }
	    #pragma warning restore IDE0002
	}

[0]: https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/basic-concepts.md#78-namespace-and-type-names
[1]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0234
[2]: https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.editorbrowsableattribute?view=net-7.0
[3]: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0002
  • Loading branch information
dellis1972 authored Jan 18, 2023
1 parent ca5022b commit 3ec1b15
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace MonoDroid.Tuner
public class FixLegacyResourceDesignerStep : LinkDesignerBase
{
internal const string DesignerAssemblyName = "_Microsoft.Android.Resource.Designer";
internal const string DesignerAssemblyNamespace = "Microsoft.Android.Resource.Designer";
internal const string DesignerAssemblyNamespace = "_Microsoft.Android.Resource.Designer";

bool designerLoaded = false;
AssemblyDefinition designerAssembly = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,25 @@ bool Run(DirectoryAssemblyResolver res)
} else {
// Add the InternalsVisibleToAttribute so the app can access ResourceConstant
if (!string.IsNullOrEmpty (AssemblyName)) {
MethodReference internalsVisibleToAttributeConstructor = ImportCustomAttributeConstructor ("System.Runtime.CompilerServices.InternalsVisibleToAttribute", module, netstandardDef.MainModule);
MethodReference internalsVisibleToAttributeConstructor = ImportCustomAttributeConstructor ("System.Runtime.CompilerServices.InternalsVisibleToAttribute", module, netstandardDef.MainModule, argCount: 1);
var ar = new CustomAttribute (internalsVisibleToAttributeConstructor);
ar.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, AssemblyName));
module.Assembly.CustomAttributes.Add (ar);
}
}

MethodReference targetFrameworkConstructor = ImportCustomAttributeConstructor ("System.Runtime.Versioning.TargetFrameworkAttribute", module, netstandardDef.MainModule);
MethodReference targetFrameworkConstructor = ImportCustomAttributeConstructor ("System.Runtime.Versioning.TargetFrameworkAttribute", module, netstandardDef.MainModule, argCount: 1);

var attr = new CustomAttribute (targetFrameworkConstructor);
attr.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, $".NETStandard,Version=v2.1"));
attr.Properties.Add (new CustomAttributeNamedArgument ("FrameworkDisplayName", new CustomAttributeArgument (module.TypeSystem.String, "")));
module.Assembly.CustomAttributes.Add (attr);

MethodReference editorBrowserConstructor = ImportCustomAttributeConstructor ("System.ComponentModel.EditorBrowsableAttribute", module, netstandardDef.MainModule, argCount: 1);
TypeReference e = ImportType ("System.ComponentModel.EditorBrowsableState", module, netstandardDef.MainModule);
var editorBrowserAttr = new CustomAttribute (editorBrowserConstructor);
editorBrowserAttr.ConstructorArguments.Add (new CustomAttributeArgument (e, System.ComponentModel.EditorBrowsableState.Never));

var att = TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.BeforeFieldInit;

intArray = new ArrayType (module.TypeSystem.Int32);
Expand All @@ -131,6 +136,7 @@ bool Run(DirectoryAssemblyResolver res)
objectRef
);
CreateCtor (resourceDesigner, module);
resourceDesigner.CustomAttributes.Add (editorBrowserAttr);
module.Types.Add (resourceDesigner);
TypeDefinition constDesigner = null;
if (IsApplication) {
Expand All @@ -143,6 +149,7 @@ bool Run(DirectoryAssemblyResolver res)
objectRef
);
CreateCtor (constDesigner, module);
constDesigner.CustomAttributes.Add (editorBrowserAttr);
module.Types.Add (constDesigner);
}

Expand Down Expand Up @@ -196,11 +203,16 @@ bool Run(DirectoryAssemblyResolver res)
return !Log.HasLoggedErrors;
}

MethodReference ImportCustomAttributeConstructor (string type, ModuleDefinition module, ModuleDefinition sourceModule = null)
MethodReference ImportCustomAttributeConstructor (string type, ModuleDefinition module, ModuleDefinition sourceModule = null, int argCount = 0)
{
var tr = module.ImportReference ((sourceModule ?? module).ExportedTypes.First(x => x.FullName == type).Resolve ());
var tv = tr.Resolve();
return module.ImportReference (tv.Methods.First(x => x.IsConstructor));
return module.ImportReference (tv.Methods.First(x => x.IsConstructor && (x.Parameters?.Count ?? 0) == argCount));
}

TypeReference ImportType (string type, ModuleDefinition module, ModuleDefinition sourceModule = null)
{
return module.ImportReference ((sourceModule ?? module).ExportedTypes.First(x => x.FullName == type).Resolve ());
}

void CreateIntProperty (string resourceClass, string propertyName, int value, TypeDefinition resourceDesigner, ModuleDefinition module,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,25 @@ public class GenerateResourceDesignerIntermediateClass : AndroidTask
private const string ResourceDesigner = $"{FixLegacyResourceDesignerStep.DesignerAssemblyNamespace}.Resource";
private const string ResourceDesignerConstants = $"{FixLegacyResourceDesignerStep.DesignerAssemblyNamespace}.ResourceConstant";

private const string CSharpTemplate = @"// This is an Auto Generated file DO NOT EDIT
private const string CSharpTemplate = @"//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool. DO NOT EDIT
// </auto-generated>
//------------------------------------------------------------------------------
using System;
namespace %NAMESPACE% {
#pragma warning disable IDE0002
public partial class Resource : %BASECLASS% {
}
#pragma warning restore IDE0002
}
";
private const string FSharpTemplate = @"// This is an Auto Generated file DO NOT EDIT
private const string FSharpTemplate = @"//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool. DO NOT EDIT
// </auto-generated>
//------------------------------------------------------------------------------
namespace %NAMESPACE%
type Resource = %BASECLASS%
Expand Down

0 comments on commit 3ec1b15

Please sign in to comment.