Skip to content

Commit

Permalink
Some rework
Browse files Browse the repository at this point in the history
  • Loading branch information
dellis1972 committed Nov 1, 2021
1 parent d4ff15e commit 89f8ff8
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Copyright (C) 2016 Xamarin. All rights reserved.
Inputs="$(IntermediateOutputPath)R.txt"
Outputs="$(_GenerateResourceDesignerAssemblyOutput)"
Condition=" '$(AndroidUseDesignerAssembly)' == 'True' And Exists ('$(IntermediateOutputPath)R.txt') ">
<ItemGroup>
<_DesignerNamespaces Include="$(RootNamespace)" />
</ItemGroup>
<!-- <ItemGroup>
<_DesignerNamespaces Include="$(AndroidResgenNamespace)" />
</ItemGroup> -->
<GenerateResourceDesignerAssembly
ContinueOnError="$(DesignTimeBuild)"
RTxtFile="$(IntermediateOutputPath)R.txt"
Expand All @@ -41,6 +41,11 @@ Copyright (C) 2016 Xamarin. All rights reserved.
TargetFrameworkIdentifier="$(TargetFrameworkIdentifier)"
TargetFrameworkVersion="$(TargetFrameworkVersion)"
UsingAndroidNETSdk="$(UsingAndroidNETSdk)"
ProjectDir="$(ProjectDir)"
Resources="@(AndroidResource);@(AndroidBoundLayout)"
ResourceDirectory="$(MonoAndroidResourcePrefix)"
AdditionalResourceDirectories="@(LibraryResourceDirectories)"
Namespace="$(AndroidResgenNamespace)"
>
</GenerateResourceDesignerAssembly>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public class GenerateResourceDesignerAssembly : AndroidTask
const string DesignerAssemblyName = "Xamarin.Android.Resource.Designer";
public override string TaskPrefix => "GRDA";

public string Namespace { get; set; }

[Required]
public ITaskItem RTxtFile { get; set; }

Expand All @@ -38,58 +40,76 @@ public class GenerateResourceDesignerAssembly : AndroidTask
[Required]
public string TargetFrameworkIdentifier { get; set; }

[Required]
public string ProjectDir { get; set; }

[Required]
public ITaskItem[] Resources { get; set; }

[Required]
public string ResourceDirectory { get; set; }

public ITaskItem[] AdditionalResourceDirectories { get; set; }

public bool UsingAndroidNETSdk { get; set; } = false;

public string[] Namespaces { get; set; }

TypeReference intArray;
TypeReference intRef;
TypeReference objectRef;
Dictionary<string, string> resource_fixup = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);

public override bool RunTask ()
{
Namespace = Namespace ?? "Application";
// ResourceDirectory may be a relative path, and
// we need to compare it to absolute paths
ResourceDirectory = Path.GetFullPath (ResourceDirectory);

BuildRenameMap (ResourceDirectory);
// Generate an assembly which contains all the values in the provided
// R.txt file.
var assembly = AssemblyDefinition.CreateAssembly (
new AssemblyNameDefinition (DesignerAssemblyName, new Version ()),
new AssemblyNameDefinition (DesignerAssemblyName, new Version (1, 0)),
DesignerAssemblyName,
ModuleKind.Dll);

var module = assembly.MainModule;

module.AssemblyReferences.Clear ();
var netstandardAsm = AssemblyNameReference.Parse ("netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51");
module.AssemblyReferences.Add(netstandardAsm);
var netstandardDef = module.AssemblyResolver.Resolve(netstandardAsm);

if (!IsApplication) {
MethodReference referenceAssemblyConstructor = module.ImportReference ( typeof (ReferenceAssemblyAttribute).GetConstructor (Type.EmptyTypes));
MethodReference referenceAssemblyConstructor = ImportCustomAttributeConstructor ("System.Runtime.CompilerServices.ReferenceAssemblyAttribute", module, netstandardDef.MainModule);
module.Assembly.CustomAttributes.Add (new CustomAttribute (referenceAssemblyConstructor));
}

MethodReference targetFrameworkConstructor = module.ImportReference (typeof (TargetFrameworkAttribute).GetConstructor(new [] { typeof (string) }));
MethodReference targetFrameworkConstructor = ImportCustomAttributeConstructor ("System.Runtime.Versioning.TargetFrameworkAttribute", module, netstandardDef.MainModule);

var attr = new CustomAttribute (targetFrameworkConstructor);
// ".NETStandard,Version=v2.0"
// {TargetFrameworkIdentifier},Version={TargetFrameworkVersion}
attr.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, $".NETStandard,Version=2.0"));
attr.Properties.Add(new CustomAttributeNamedArgument("FrameworkDisplayName", new CustomAttributeArgument(module.TypeSystem.String, "")));
attr.Properties.Add (new CustomAttributeNamedArgument("FrameworkDisplayName", new CustomAttributeArgument(module.TypeSystem.String, "")));
module.Assembly.CustomAttributes.Add (attr);

// if (UsingAndroidNETSdk) {
// // add .net 6 specific attributes we might not need these.
// // TargetPlatform
// // SupportedOSPlatform
// }
var att = TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.BeforeFieldInit;

// if (!UsingAndroidNETSdk)
// module.AssemblyReferences.Add(AssemblyNameReference.Parse("mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"));
// module.AssemblyReferences.Add(AssemblyNameReference.Parse("Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065"));
// module.AssemblyReferences.Add(AssemblyNameReference.Parse("System, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"));

var att = TypeAttributes.Class | TypeAttributes.Public;
intArray = new ArrayType (module.TypeSystem.Int32);
intRef = module.TypeSystem.Int32;
objectRef = module.TypeSystem.Object;

var resourceDesigner = new TypeDefinition(
DesignerAssemblyName,
"Xamarin.Android.Resource.Designer",
"Resource",
att,
module.TypeSystem.Object);
objectRef);
CreateCtor (resourceDesigner, module);
module.Types.Add(resourceDesigner);

intArray = module.ImportReference(typeof(int[]));

ProcessRtxtFile (RTxtFile.ItemSpec, resourceDesigner, module);

foreach (var ns in (Namespaces ?? Array.Empty<string> ())) {
Expand All @@ -100,13 +120,20 @@ public override bool RunTask ()
return !Log.HasLoggedErrors;
}

MethodReference ImportCustomAttributeConstructor (string type, ModuleDefinition module, ModuleDefinition sourceModule)
{
var tr = module.ImportReference (sourceModule.ExportedTypes.First(x => x.FullName == type).Resolve ());
var tv = tr.Resolve();
return module.ImportReference (tv.Methods.First(x => x.IsConstructor));
}

void ProcessRtxtFile (string file, TypeDefinition resourceDesigner, ModuleDefinition module)
{
var lines = System.IO.File.ReadLines (file);
foreach (var line in lines) {
var items = line.Split (new char [] { ' ' }, 4);
int value = items [1] != "styleable" ? Convert.ToInt32 (items [3], 16) : -1;
string itemName = items [2];
string itemName = GetResourceName(items [1], items [2], resource_fixup);
switch (items [1]) {
case "anim":
case "animator":
Expand Down Expand Up @@ -158,35 +185,48 @@ void CreateIntProperty (string resourceClass, string propertyName, int value, Ty
TypeDefinition nestedType = CreateResourceClass (resourceDesigner, resourceClass, module);
PropertyDefinition p = CreateProperty (propertyName, value, module);
nestedType.Properties.Add (p);
nestedType.Methods.Add (p.GetMethod);
nestedType.Methods.Insert (Math.Max(0, nestedType.Methods.Count () -1), p.GetMethod);
}

void CreateIntArrayProperty (string resourceClass, string propertyName, int[] values, TypeDefinition resourceDesigner, ModuleDefinition module)
{
TypeDefinition nestedType = CreateResourceClass (resourceDesigner, resourceClass, module);
PropertyDefinition p = CreateArrayProperty (propertyName, values, module);
nestedType.Properties.Add (p);
nestedType.Methods.Add (p.GetMethod);
nestedType.Methods.Insert (Math.Max(0, nestedType.Methods.Count () -1), p.GetMethod);
}

Dictionary<string, TypeDefinition> resourceClasses = new Dictionary<string, TypeDefinition> ();

void CreateCtor (TypeDefinition type, ModuleDefinition module)
{
var ctor = new MethodDefinition(".ctor", MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, module.TypeSystem.Void);
var ctoril = ctor.Body.GetILProcessor();
ctoril.Emit(OpCodes.Ldarg_0);
var o = module.TypeSystem.Object.Resolve();
ctoril.Emit(OpCodes.Call, module.ImportReference(o.Methods.First(x => x.IsConstructor)));
ctoril.Emit(OpCodes.Nop);
ctoril.Emit(OpCodes.Ret);
type.Methods.Add(ctor);
}

TypeDefinition CreateResourceClass (TypeDefinition resourceDesigner, string className, ModuleDefinition module)
{
string name = ResourceParser.GetNestedTypeName (className);
if (resourceClasses.ContainsKey (name)) {
return resourceClasses[name];
}
var resourceClass = new TypeDefinition (DesignerAssemblyName, name, TypeAttributes.Class | TypeAttributes.Public, module.TypeSystem.Object);
var resourceClass = new TypeDefinition (string.Empty, name, TypeAttributes.Class | TypeAttributes.NestedPublic | TypeAttributes.BeforeFieldInit | TypeAttributes.Sealed, objectRef);
CreateCtor(resourceClass, module);
resourceDesigner.NestedTypes.Add(resourceClass);
resourceClasses [name] = resourceClass;
return resourceClass;
}

PropertyDefinition CreateProperty (string propertyName, int value, ModuleDefinition module)
{
var p = new PropertyDefinition (propertyName, PropertyAttributes.None, module.TypeSystem.Int32);
var getter = new MethodDefinition ($"{propertyName}_get", MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Int32);
var p = new PropertyDefinition (propertyName, PropertyAttributes.None, intRef);
var getter = new MethodDefinition ($"get_{propertyName}", MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static, intRef);
p.GetMethod = getter;
p.SetMethod = null;
var il = p.GetMethod.Body.GetILProcessor ();
Expand All @@ -198,12 +238,12 @@ PropertyDefinition CreateProperty (string propertyName, int value, ModuleDefinit
PropertyDefinition CreateArrayProperty (string propertyName, IEnumerable<int> values, ModuleDefinition module)
{
var p = new PropertyDefinition (propertyName, PropertyAttributes.None, intArray);
var getter = new MethodDefinition ($"{propertyName}_get", MethodAttributes.Public | MethodAttributes.Static, intArray);
var getter = new MethodDefinition ($"get_{propertyName}", MethodAttributes.Public | MethodAttributes.Static, intArray);
p.GetMethod = getter;
p.SetMethod = null;
var il = p.GetMethod.Body.GetILProcessor ();
il.Emit (OpCodes.Ldc_I4, values.Count ());
il.Emit (OpCodes.Newarr, module.TypeSystem.Int32);
il.Emit (OpCodes.Newarr, intRef);
int index = 0;
foreach (int value in values) {
il.Emit (OpCodes.Dup);
Expand All @@ -214,5 +254,98 @@ PropertyDefinition CreateArrayProperty (string propertyName, IEnumerable<int> va
il.Emit (OpCodes.Ret);
return p;
}

// TODO: Borrowed code!
internal string GetResourceName (string type, string name, Dictionary<string, string> map)
{
string mappedValue;
string key = string.Format ("{0}{1}{2}", type, Path.DirectorySeparatorChar, name).ToLowerInvariant ();

if (map.TryGetValue (key, out mappedValue)) {
Log.LogDebugMessage (" - Remapping resource: {0}.{1} -> {2}", type, name, mappedValue);
return ResourceIdentifier.CreateValidIdentifier (mappedValue.Substring (mappedValue.LastIndexOf (Path.DirectorySeparatorChar) + 1));
}

Log.LogDebugMessage (" - Not remapping resource: {0}.{1}", type, name);

return ResourceIdentifier.CreateValidIdentifier (name);
}

private void BuildRenameMap (string resourceDirectory)
{
// Create our capitalization maps so we can support mixed case resources
foreach (var item in Resources) {
var path = Path.GetFullPath (item.ItemSpec);
if (!path.StartsWith (resourceDirectory, StringComparison.OrdinalIgnoreCase))
continue;

var name = path.Substring (resourceDirectory.Length).TrimStart ('/', '\\');
var logical_name = item.GetMetadata ("LogicalName").Replace ('\\', '/');
if (string.IsNullOrEmpty (logical_name))
logical_name = Path.GetFileName (path);

AddRename (name.Replace ('/', Path.DirectorySeparatorChar), logical_name.Replace ('/', Path.DirectorySeparatorChar));
}
if (AdditionalResourceDirectories != null) {
foreach (var additionalDir in AdditionalResourceDirectories) {
var dir = Path.Combine (ProjectDir, Path.GetDirectoryName (additionalDir.ItemSpec));
var file = Path.Combine (dir, "__res_name_case_map.txt");
if (!File.Exists (file)) {
// .NET 6 .aar files place the file in a sub-directory
file = Path.Combine (dir, ".net", "__res_name_case_map.txt");
if (!File.Exists (file))
continue;
}
foreach (var line in File.ReadAllLines (file).Where (l => !string.IsNullOrEmpty (l))) {
string [] tok = line.Split (';');
AddRename (tok [1].Replace ('/', Path.DirectorySeparatorChar), tok [0].Replace ('/', Path.DirectorySeparatorChar));
}
}
}
}

private void AddRename (string android, string user)
{
var from = android;
var to = user;

if (from.Contains ('.'))
from = from.Substring (0, from.LastIndexOf ('.'));
if (to.Contains ('.'))
to = to.Substring (0, to.LastIndexOf ('.'));

from = NormalizeAlternative (from);
to = NormalizeAlternative (to);

string curTo;

if (resource_fixup.TryGetValue (from, out curTo)) {
if (string.Compare (to, curTo, StringComparison.OrdinalIgnoreCase) != 0) {
var ext = Path.GetExtension (android);
var dir = Path.GetDirectoryName (user);

Log.LogDebugMessage ("Resource target names differ; got '{0}', expected '{1}'.",
Path.Combine (dir, Path.GetFileName (to) + ext),
Path.Combine (dir, Path.GetFileName (curTo) + ext));
}
return;
}

resource_fixup.Add (from, to);
}

static string NormalizeAlternative (string value)
{
int s = value.IndexOfAny (new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });

if (s < 0)
return value;

int a = value.IndexOf ('-');

return
ResourceParser.GetNestedTypeName (value.Substring (0, (a < 0 || a >= s) ? s : a)).ToLowerInvariant () +
value.Substring (s);
}
}
}

0 comments on commit 89f8ff8

Please sign in to comment.