Skip to content

Commit

Permalink
First Pass at using Resource Designer assembly
Browse files Browse the repository at this point in the history
  • Loading branch information
dellis1972 committed Oct 26, 2021
1 parent b1180c8 commit 95e7992
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 1 deletion.
1 change: 1 addition & 0 deletions build-tools/installers/create-installers.targets
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.CSharp.targets" ExcludeFromAndroidNETSdk="true" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.D8.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Designer.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Resource.Designer.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.DesignTime.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.DX.targets" ExcludeFromAndroidNETSdk="true" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.EmbeddedResource.targets" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_Aapt2ProguardRules Condition=" '$(AndroidLinkTool)' != '' ">$(IntermediateOutputPath)aapt_rules.txt</_Aapt2ProguardRules>
</PropertyGroup>
<Aapt2Link
Condition=" '$(_AndroidResourceDesignerFile)' != '' "
Condition=" '$(_AndroidResourceDesignerFile)' != '' Or '$(AndroidUseDesignerAssembly)' == 'True' "
ContinueOnError="$(DesignTimeBuild)"
DaemonMaxInstanceCount="$(Aapt2DaemonMaxInstanceCount)"
DaemonKeepInDomain="$(_Aapt2DaemonKeepInDomain)"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!--
***********************************************************************************************
Xamarin.Android.Resource.Designer.targets
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
This file imports the version- and platform-specific targets for the project importing
this file. This file also defines targets to produce an error if the specified targets
file does not exist, but the project is built anyway (command-line or IDE build).
Copyright (C) 2016 Xamarin. All rights reserved.
***********************************************************************************************
-->

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<UsingTask TaskName="Xamarin.Android.Tasks.GenerateResourceDesignerAssembly" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />

<PropertyGroup>
<AndroidUseDesignerAssembly Condition=" '$(AndroidUseIntermediateDesignerFile)' == 'False' And '$(AndroidUseDesignerAssembly)' == '' ">True</AndroidUseDesignerAssembly>
<AndroidUseDesignerAssembly Condition=" '$(AndroidUseDesignerAssembly)' == '' ">False</AndroidUseDesignerAssembly>
<_GenerateResourceDesignerAssemblyOutput>$(IntermediateOutputPath)Xamarin.Android.Resource.Designer.dll</_GenerateResourceDesignerAssemblyOutput>
</PropertyGroup>

<Target Name="_GenerateResourceDesignerAssembly"
Inputs="$(IntermediateOutputPath)R.txt"
Outputs="$(_GenerateResourceDesignerAssemblyOutput)"
Condition=" '$(AndroidUseDesignerAssembly)' == 'True' And Exists ('$(IntermediateOutputPath)R.txt') ">
<ItemGroup>
<_DesignerNamespaces Include="$(RootNamespace)" />
</ItemGroup>
<GenerateResourceDesignerAssembly
ContinueOnError="$(DesignTimeBuild)"
RTxtFile="$(IntermediateOutputPath)R.txt"
IsApplication="$(AndroidApplication)"
DesignTimeBuild="$(DesignTimeBuild)"
OutputFile="$(_GenerateResourceDesignerAssemblyOutput)"
Namespaces="@(_DesignerNamespaces)"
TargetFrameworkIdentifier="$(TargetFrameworkIdentifier)"
TargetFrameworkVersion="$(TargetFrameworkVersion)"
UsingAndroidNETSdk="$(UsingAndroidNETSdk)"
>
</GenerateResourceDesignerAssembly>
<ItemGroup>
<FileWrites Include="$(_GenerateResourceDesignerAssemblyOutput)" />
<ReferencePath Include="$(_GenerateResourceDesignerAssemblyOutput)">
<CopyLocal>true</CopyLocal>
</ReferencePath>
<Compile Remove="$(_AndroidResourceDesignerFile)" />
</ItemGroup>
</Target>

<PropertyGroup>
<BuildResourceDesignerDependsOn>
$(_GenerateResourceDesignerAssembly)
</BuildResourceDesignerDependsOn>
</PropertyGroup>

<Target Name="BuildResourceDesigner" Condition=" '$(AndroidUseDesignerAssembly)' == 'True' "
DependsOnTargets="$(BuildResourceDesignerDependsOn)" />

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ projects, these properties are set in Xamarin.Android.Legacy.targets.
_CheckForDeletedResourceFile;
_ComputeAndroidResourcePaths;
_UpdateAndroidResgen;
_GenerateResourceDesignerAssembly;
_CreateAar;
</_UpdateAndroidResourcesDependsOn>
<CompileDependsOn>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright (C) 2011 Xamarin, Inc. All rights reserved.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Android.Build.Tasks;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Xamarin.Android.Tasks
{
public class GenerateResourceDesignerAssembly : AndroidTask
{
const string DesignerAssemblyName = "Xamarin.Android.Resource.Designer";
public override string TaskPrefix => "GRDA";

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

public ITaskItem ResourceMap { get; set; }

[Required]
public bool IsApplication { get; set; }

[Required]
public bool DesignTimeBuild { get; set; }

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

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

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

[Required]
public bool UsingAndroidNETSdk { get; set; }

public string[] Namespaces { get; set; }

public override bool RunTask ()
{
// Generate an assembly which contains all the values in the provided
// R.txt file.
var assembly = AssemblyDefinition.CreateAssembly (
new AssemblyNameDefinition (DesignerAssemblyName, new Version ()),
DesignerAssemblyName,
ModuleKind.Dll);

var module = assembly.MainModule;

if (!IsApplication) {
MethodReference referenceAssemblyConstructor = module.ImportReference ( typeof (ReferenceAssemblyAttribute).GetConstructor (Type.EmptyTypes));
module.Assembly.CustomAttributes.Add (new CustomAttribute (referenceAssemblyConstructor));
}

MethodReference targetFrameworkConstructor = module.ImportReference (typeof (TargetFrameworkAttribute).GetConstructor(new [] { typeof (string) }));
var attr = new CustomAttribute (targetFrameworkConstructor);
attr.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, $"{TargetFrameworkIdentifier},Version={TargetFrameworkVersion}"));
module.Assembly.CustomAttributes.Add (attr);

if (UsingAndroidNETSdk) {
// add .net 6 specific attributes we might not need these.
// TargetPlatform
// SupportedOSPlatform
}

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;

var resourceDesigner = new TypeDefinition(
DesignerAssemblyName,
"Resource",
att,
module.TypeSystem.Object);
module.Types.Add(resourceDesigner);

ProcessRtxtFile (RTxtFile.ItemSpec, resourceDesigner, module);

foreach (var ns in (Namespaces ?? Array.Empty<string> ())) {
module.Types.Add (new TypeDefinition (ns, "Resource", att, resourceDesigner));
}

assembly.Write (OutputFile.ItemSpec);
return !Log.HasLoggedErrors;
}

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];
switch (items [1]) {
case "anim":
case "animator":
case "attr":
case "array":
case "bool":
case "color":
case "dimen":
case "drawable":
case "font":
case "id":
case "integer":
case "interpolator":
case "layout":
case "menu":
case "mipmap":
case "plurals":
case "raw":
case "string":
case "style":
case "transition":
case "xml":
CreateIntProperty (items [1], itemName, value, resourceDesigner, module);
break;
// case "styleable":
// switch (items [0]) {
// case "int":
// CreateIntField (styleable, itemName, Convert.ToInt32 (items [3], 10));
// break;
// case "int[]":
// var arrayValues = items [3].Trim (new char [] { '{', '}' })
// .Replace (" ", "")
// .Split (new char [] { ',' });
// CreateIntArrayField (styleable, itemName, arrayValues.Length,
// arrayValues.Select (x => string.IsNullOrEmpty (x) ? -1 : Convert.ToInt32 (x, 16)).ToArray ());
// break;
// }
// break;
// for custom views
default:
CreateIntProperty (items [1], itemName, value, resourceDesigner, module);
break;
}
}
}

void CreateIntProperty (string resourceClass, string propertyName, int value, TypeDefinition resourceDesigner, ModuleDefinition module)
{
TypeDefinition nestedType = CreateResourceClass (resourceDesigner, resourceClass, module);
PropertyDefinition p = CreateProperty (propertyName, value, module);
nestedType.Properties.Add (p);
nestedType.Methods.Add (p.GetMethod);
}

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

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);
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);
p.GetMethod = getter;
p.SetMethod = null;
var il = p.GetMethod.Body.GetILProcessor ();
il.Emit (OpCodes.Ldc_I4, value);
il.Emit (OpCodes.Ret);
return p;
}
}
}
1 change: 1 addition & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/LinkAssemblies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ bool Execute (DirectoryAssemblyResolver res)
{
// Put every assembly we'll need in the resolver
foreach (var assembly in ResolvedAssemblies) {
Log.LogDebugMessage ($"Preloading {assembly.ItemSpec}");
res.Load (Path.GetFullPath (assembly.ItemSpec));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>Xamarin.Android.Designer.targets</Link>
</None>
<None Include="MSBuild\Xamarin\Android\Xamarin.Android.Resource.Designer.targets">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>Xamarin.Android.Resource.Designer.targets</Link>
</None>
<None Include="MSBuild\Xamarin\Android\Xamarin.Android.Aapt.targets">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>Xamarin.Android.Aapt.targets</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.Aapt2.targets" Condition=" '$(AndroidUseAapt2)' == 'true' or '$(UsingAndroidNETSdk)' == 'true' " />
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.DesignTime.targets" />
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.EmbeddedResource.targets" />
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.Resource.Designer.targets" />
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.SkipCases.projitems" />
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.Tooling.targets" />
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.Legacy.targets" Condition=" '$(UsingAndroidNETSdk)' != 'True' " />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ projects. .NET 5 projects will not import this file.
_CheckForDeletedResourceFile;
_ComputeAndroidResourcePaths;
_UpdateAndroidResgen;
_GenerateResourceDesignerAssembly;
_CreateManagedLibraryResourceArchive;
</_UpdateAndroidResourcesDependsOn>
<GetAndroidDependenciesDependsOn>
Expand Down Expand Up @@ -298,6 +299,10 @@ projects. .NET 5 projects will not import this file.
Include="$(OutDir)$(TargetFileName)"
Condition="Exists ('$(OutDir)$(TargetFileName)')"
/>
<FilteredAssemblies
Include="$(_GenerateResourceDesignerAssemblyOutput)"
Condition="Exists ('$(_GenerateResourceDesignerAssemblyOutput)')"
/>
<FilteredAssemblies
Include="@(ReferenceCopyLocalPaths)"
Condition="'%(ReferenceCopyLocalPaths.ResolvedFrom)' != 'ImplicitlyExpandDesignTimeFacades' And '%(ReferenceCopyLocalPaths.Extension)' == '.dll' And '%(ReferenceCopyLocalPaths.RelativeDir)' == '' And Exists('%(ReferenceCopyLocalPaths.Identity)') "
Expand Down

0 comments on commit 95e7992

Please sign in to comment.