[Xamarin.Android.Build.Tasks] Implement Managed Resource Parser (#787)

Implement a Managed Resource Parser for DesignTime Builds on Windows,
which avoids use of `aapt`.

One of our (many) problems in the DesignTime build is the amount of
time we need to spend in `aapt`. Our current Resource Designer
generator process is as follows.

 1. Copy all the resources into one folder.
 2. Correct the Casing on those resources so they are lowercase
    while maintaining a map of the changes we made.
 3. Call `aapt` to generate a `R.java` file which contains all the
    Id's for the required resources.
 4. Parse the `R.java` file and create a C# Code Dom to represent
    the Resource.Designer.cs. Note we also need to convert the lowercased
    resource back into the case from the app so the C# code will match
    what the user typed in.
 5. Finally write out the `Resource.Designer.cs`

So we do a TON of stuff there.

For a design time build we don't really need to do all of that stuff.
Firstly because its a design time build (not a real one) we don't
care about what the values of the ids are. We just need to have a
good `Resource.Designer.cs` class. We also don't need to convert the
casing back and forth. We can just use the casing the user defines
because that is what we normally end up with anyway.
We don't need to generate a `R.java` as we can operate on the
projects `Resources` folder directly.
This commit is contained in:
Dean Ellis 2017-09-05 20:38:17 +01:00 коммит произвёл Jonathan Pryor
Родитель d980ebae64
Коммит 10d1213bff
9 изменённых файлов: 626 добавлений и 53 удалений

Просмотреть файл

@ -587,6 +587,12 @@ when packaing Release applications.
Added in Xamarin.Android 7.2.
- **AndroidUseManagedDesignTimeResourceGenerator** – A boolean property which
will switch over the design time builds to use the managed resource parser rather
than `aapt`.
Added in Xamarin.Android 7.3.
## Binding Project Build Properties
The following MSBuild properties are used with

Просмотреть файл

@ -38,6 +38,9 @@ namespace Xamarin.Android.Tasks
public ITaskItem[] References { get; set; }
[Required]
public bool UseManagedResourceGenerator { get; set; }
private Dictionary<string, string> resource_fixup = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
public override bool Execute ()
@ -56,10 +59,11 @@ namespace Xamarin.Android.Tasks
Log.LogDebugMessage (" ResourceDirectory: {0}", ResourceDirectory);
Log.LogDebugTaskItemsAndLogical (" AdditionalResourceDirectories:", AdditionalResourceDirectories);
Log.LogDebugMessage (" IsApplication: {0}", IsApplication);
Log.LogDebugMessage (" UseManagedResourceGenerator: {0}", UseManagedResourceGenerator);
Log.LogDebugTaskItemsAndLogical (" Resources:", Resources);
Log.LogDebugTaskItemsAndLogical (" References:", References);
if (!File.Exists (JavaResgenInputFile))
if (!File.Exists (JavaResgenInputFile) && !UseManagedResourceGenerator)
return true;
// ResourceDirectory may be a relative path, and
@ -89,8 +93,14 @@ namespace Xamarin.Android.Tasks
}
// Parse out the resources from the R.java file
JavaResourceParser.Log = Log;
var resources = JavaResourceParser.Parse (JavaResgenInputFile, IsApplication, resource_fixup);
CodeTypeDeclaration resources;
if (UseManagedResourceGenerator) {
var parser = new ManagedResourceParser () { Log = Log };
resources = parser.Parse (ResourceDirectory, AdditionalResourceDirectories?.Select (x => x.ItemSpec), IsApplication, resource_fixup);
} else {
var parser = new JavaResourceParser () { Log = Log };
resources = parser.Parse (JavaResgenInputFile, IsApplication, resource_fixup);
}
var extension = Path.GetExtension (NetResgenOutputFile);
var language = string.Compare (extension, ".fs", StringComparison.OrdinalIgnoreCase) == 0 ? "F#" : CodeDomProvider.GetLanguageFromExtension (extension);
@ -269,7 +279,7 @@ namespace Xamarin.Android.Tasks
int a = value.IndexOf ('-');
return
JavaResourceParser.GetNestedTypeName (value.Substring (0, (a < 0 || a >= s) ? s : a)).ToLowerInvariant () +
ResourceParser.GetNestedTypeName (value.Substring (0, (a < 0 || a >= s) ? s : a)).ToLowerInvariant () +
value.Substring (s);
}
}

Просмотреть файл

@ -73,7 +73,7 @@ using System.Runtime.CompilerServices;
new BuildItem.ProjectReference (@"..\Lib1\Lib1.csproj", lib.ProjectName, lib.ProjectGuid),
},
};
proj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "False");
using (var l = CreateDllBuilder (Path.Combine (path, lib.ProjectName), false, false)) {
using (var b = CreateApkBuilder (Path.Combine (path, proj.ProjectName), false, false)) {
l.Verbosity = LoggerVerbosity.Diagnostic;
@ -430,6 +430,7 @@ namespace UnnamedProject
IsRelease = isRelease,
};
proj.SetProperty ("AndroidUseIntermediateDesignerFile", "True");
proj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "False");
using (var b = CreateApkBuilder ("temp/CheckOldResourceDesignerIsNotUsed")) {
var designer = Path.Combine ("Resources", "Resource.designer" + proj.Language.DefaultDesignerExtension);
if (File.Exists (designer))
@ -938,5 +939,140 @@ namespace Lib1 {
}
}
}
[Test]
public void BuildAppWithManagedResourceParser()
{
var path = Path.Combine ("temp", "BuildAppWithManagedResourceParser");
var appProj = new XamarinAndroidApplicationProject () {
IsRelease = true,
ProjectName = "App1",
};
appProj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
using (var appBuilder = CreateApkBuilder (Path.Combine (path, appProj.ProjectName))) {
appBuilder.Verbosity = LoggerVerbosity.Diagnostic;
appBuilder.Target = "Compile";
Assert.IsTrue (appBuilder.Build (appProj, parameters: new string[] { "DesignTimeBuild=true"} ),
"DesignTime Application Build should have succeeded.");
Assert.IsFalse (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"),
"Target '_ManagedUpdateAndroidResgen' should have run.");
var designerFile = Path.Combine (Root, path, appProj.ProjectName, appProj.IntermediateOutputPath, "designtime", "Resource.Designer.cs");
FileAssert.Exists (designerFile, $"'{designerFile}' should have been created.");
var designerContents = File.ReadAllText (designerFile);
StringAssert.Contains ("hello", designerContents, $"{designerFile} should contain Resources.Strings.hello");
StringAssert.Contains ("app_name", designerContents, $"{designerFile} should contain Resources.Strings.app_name");
StringAssert.Contains ("myButton", designerContents, $"{designerFile} should contain Resources.Id.myButton");
StringAssert.Contains ("Icon", designerContents, $"{designerFile} should contain Resources.Drawable.Icon");
StringAssert.Contains ("Main", designerContents, $"{designerFile} should contain Resources.Layout.Main");
appBuilder.Target = "SignAndroidPackage";
Assert.IsTrue (appBuilder.Build (appProj),
"Normal Application Build should have succeeded.");
Assert.IsTrue (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"),
"Target '_ManagedUpdateAndroidResgen' should not have run.");
Assert.IsTrue (appBuilder.Clean (appProj), "Clean should have succeeded");
Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned.");
}
}
[Test]
public void BuildAppWithManagedResourceParserAndLibraries ()
{
int maxBuildTimeMs = 5000;
var path = Path.Combine ("temp", "BuildAppWithManagedResourceParserAndLibraries");
var theme = new AndroidItem.AndroidResource ("Resources\\values\\Theme.xml") {
TextContent = () => @"<?xml version=""1.0"" encoding=""utf-8""?>
<resources>
<color name=""theme_devicedefault_background"">#ffffffff</color>
<color name=""SomeColor"">#ffffffff</color>
</resources>",
};
var libProj = new XamarinAndroidLibraryProject () {
IsRelease = true,
ProjectName = "Lib1",
AndroidResources = {
theme,
},
};
libProj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
var appProj = new XamarinAndroidApplicationProject () {
IsRelease = true,
ProjectName = "App1",
References = {
new BuildItem.ProjectReference (@"..\Lib1\Lib1.csproj", libProj.ProjectName, libProj.ProjectGuid),
},
Packages = {
KnownPackages.SupportMediaCompat_25_4_0_1,
KnownPackages.SupportFragment_25_4_0_1,
KnownPackages.SupportCoreUtils_25_4_0_1,
KnownPackages.SupportCoreUI_25_4_0_1,
KnownPackages.SupportCompat_25_4_0_1,
KnownPackages.AndroidSupportV4_25_4_0_1,
KnownPackages.SupportV7AppCompat_25_4_0_1,
},
};
appProj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
using (var libBuilder = CreateDllBuilder (Path.Combine (path, libProj.ProjectName), false, false)) {
libBuilder.Verbosity = LoggerVerbosity.Diagnostic;
using (var appBuilder = CreateApkBuilder (Path.Combine (path, appProj.ProjectName), false, false)) {
appBuilder.Verbosity = LoggerVerbosity.Diagnostic;
libBuilder.Target = "Compile";
Assert.IsTrue (libBuilder.Build (libProj, parameters: new string [] { "DesignTimeBuild=true" }), "Library project should have built");
Assert.LessOrEqual (libBuilder.LastBuildTime.TotalMilliseconds, maxBuildTimeMs, "DesingTime build should be less than 5 seconds.");
Assert.IsFalse (libProj.CreateBuildOutput (libBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"),
"Target '_ManagedUpdateAndroidResgen' should have run.");
appBuilder.Target = "Compile";
Assert.IsTrue (appBuilder.Build (appProj, parameters: new string [] { "DesignTimeBuild=true" }), "Library project should have built");
Assert.LessOrEqual (appBuilder.LastBuildTime.TotalMilliseconds, maxBuildTimeMs, "DesingTime build should be less than 5 seconds.");
Assert.IsFalse (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"),
"Target '_ManagedUpdateAndroidResgen' should have run.");
var designerFile = Path.Combine (Root, path, appProj.ProjectName, appProj.IntermediateOutputPath, "designtime", "Resource.Designer.cs");
FileAssert.Exists (designerFile, $"'{designerFile}' should have been created.");
var designerContents = File.ReadAllText (designerFile);
StringAssert.Contains ("hello", designerContents, $"{designerFile} should contain Resources.Strings.hello");
StringAssert.Contains ("app_name", designerContents, $"{designerFile} should contain Resources.Strings.app_name");
StringAssert.Contains ("myButton", designerContents, $"{designerFile} should contain Resources.Id.myButton");
StringAssert.Contains ("Icon", designerContents, $"{designerFile} should contain Resources.Drawable.Icon");
StringAssert.Contains ("Main", designerContents, $"{designerFile} should contain Resources.Layout.Main");
StringAssert.Contains ("material_grey_50", designerContents, $"{designerFile} should contain Resources.Color.material_grey_50");
StringAssert.DoesNotContain ("theme_devicedefault_background", designerContents, $"{designerFile} should not contain Resources.Color.theme_devicedefault_background");
libBuilder.Target = "Build";
Assert.IsTrue (libBuilder.Build (libProj), "Library project should have built");
Assert.IsTrue (libProj.CreateBuildOutput (libBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"),
"Target '_ManagedUpdateAndroidResgen' should not have run.");
appBuilder.Target = "Compile";
Assert.IsTrue (appBuilder.Build (appProj, parameters: new string [] { "DesignTimeBuild=true" }), "App project should have built");
Assert.LessOrEqual (appBuilder.LastBuildTime.TotalMilliseconds, maxBuildTimeMs, "DesingTime build should be less than 5 seconds.");
Assert.IsFalse (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"),
"Target '_ManagedUpdateAndroidResgen' should have run.");
FileAssert.Exists (designerFile, $"'{designerFile}' should have been created.");
designerContents = File.ReadAllText (designerFile);
StringAssert.Contains ("hello", designerContents, $"{designerFile} should contain Resources.Strings.hello");
StringAssert.Contains ("app_name", designerContents, $"{designerFile} should contain Resources.Strings.app_name");
StringAssert.Contains ("myButton", designerContents, $"{designerFile} should contain Resources.Id.myButton");
StringAssert.Contains ("Icon", designerContents, $"{designerFile} should contain Resources.Drawable.Icon");
StringAssert.Contains ("Main", designerContents, $"{designerFile} should contain Resources.Layout.Main");
StringAssert.Contains ("material_grey_50", designerContents, $"{designerFile} should contain Resources.Color.material_grey_50");
StringAssert.Contains ("theme_devicedefault_background", designerContents, $"{designerFile} should contain Resources.Color.theme_devicedefault_background");
StringAssert.Contains ("SomeColor", designerContents, $"{designerFile} should contain Resources.Color.SomeColor");
appBuilder.Target = "SignAndroidPackage";
Assert.IsTrue (appBuilder.Build (appProj), "App project should have built");
Assert.IsTrue (appBuilder.Clean (appProj), "Clean should have succeeded");
Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned.");
designerFile = Path.Combine (Root, path, libProj.ProjectName, libProj.IntermediateOutputPath, "designtime", "Resource.Designer.cs");
Assert.IsTrue (libBuilder.Clean (libProj), "Clean should have succeeded");
Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned.");
}
}
}
}
}

Просмотреть файл

@ -1760,6 +1760,7 @@ public class Test
var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
};
proj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "False");
using (var builder = CreateApkBuilder (Path.Combine ("temp", TestContext.CurrentContext.Test.Name), false ,false)) {
builder.Verbosity = LoggerVerbosity.Diagnostic;
builder.Target = "UpdateAndroidResources";

Просмотреть файл

@ -9,11 +9,9 @@ using Microsoft.Build.Utilities;
namespace Xamarin.Android.Tasks
{
class JavaResourceParser
class JavaResourceParser : ResourceParser
{
public static TaskLoggingHelper Log { get; set; }
public static CodeTypeDeclaration Parse (string file, bool isApp, Dictionary<string, string> resourceMap)
public CodeTypeDeclaration Parse (string file, bool isApp, Dictionary<string, string> resourceMap)
{
if (!File.Exists (file))
throw new InvalidOperationException ("Specified Java resource file was not found: " + file);
@ -49,18 +47,22 @@ namespace Xamarin.Android.Tasks
// }
// }
// }
static List<KeyValuePair<Regex, Func<Match, bool, CodeTypeDeclaration, Dictionary<string, string>, CodeTypeDeclaration>>> Parser = new List<KeyValuePair<Regex, Func<Match, bool, CodeTypeDeclaration, Dictionary<string, string>, CodeTypeDeclaration>>> () {
List<KeyValuePair<Regex, Func<Match, bool, CodeTypeDeclaration, Dictionary<string, string>, CodeTypeDeclaration>>> Parser;
public JavaResourceParser ()
{
Parser = new List<KeyValuePair<Regex, Func<Match, bool, CodeTypeDeclaration, Dictionary<string, string>, CodeTypeDeclaration>>> () {
Parse ("^public final class R {",
(m, app, _, map) => {
var decl = new CodeTypeDeclaration ("Resource") {
IsPartial = true,
};
var asm = Assembly.GetExecutingAssembly().GetName();
var codeAttrDecl =
var codeAttrDecl =
new CodeAttributeDeclaration("System.CodeDom.Compiler.GeneratedCodeAttribute",
new CodeAttributeArgument(
new CodeAttributeArgument(
new CodePrimitiveExpression(asm.Name)),
new CodeAttributeArgument(
new CodeAttributeArgument(
new CodePrimitiveExpression(asm.Version.ToString()))
);
decl.CustomAttributes.Add(codeAttrDecl);
@ -116,44 +118,6 @@ namespace Xamarin.Android.Tasks
return g;
}),
};
internal static int ToInt32 (string value, int @base)
{
try {
return Convert.ToInt32 (value, @base);
} catch (Exception e) {
throw new NotSupportedException (
string.Format ("Could not convert value '{0}' (base '{1}') into an Int32.",
value, @base),
e);
}
}
internal static string GetNestedTypeName (string name)
{
switch (name) {
case "anim": return "Animation";
case "attr": return "Attribute";
case "bool": return "Boolean";
case "dimen": return "Dimension";
default: return char.ToUpperInvariant (name[0]) + name.Substring (1);
}
}
static 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.LogMessage (" - Remapping resource: {0}.{1} -> {2}", type, name, mappedValue);
return mappedValue.Substring (mappedValue.LastIndexOf (Path.DirectorySeparatorChar) + 1);
}
Log.LogMessage (" - Not remapping resource: {0}.{1}", type, name);
return name;
}
}
}

Просмотреть файл

@ -0,0 +1,345 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
namespace Xamarin.Android.Tasks
{
class ManagedResourceParser : ResourceParser
{
CodeTypeDeclaration resources;
CodeTypeDeclaration layout, ids, drawable, strings, colors, dimension, raw, animation, attrib, boolean, ints, styleable, style, arrays;
Dictionary<string, string> map;
void SortMembers (CodeTypeDeclaration decl)
{
CodeTypeMember [] members = new CodeTypeMember [decl.Members.Count];
decl.Members.CopyTo (members, 0);
decl.Members.Clear ();
Array.Sort (members, (x, y) => string.Compare (x.Name, y.Name, StringComparison.OrdinalIgnoreCase));
decl.Members.AddRange (members);
}
public CodeTypeDeclaration Parse (string resourceDirectory, IEnumerable<string> additionalResourceDirectories, bool isApp, Dictionary<string, string> resourceMap)
{
if (!Directory.Exists (resourceDirectory))
throw new ArgumentException ("Specified resource directory was not found: " + resourceDirectory);
map = resourceMap ?? new Dictionary<string, string> ();
resources = CreateResourceClass ();
animation = CreateClass ("Animation");
arrays = CreateClass ("Array");
attrib = CreateClass ("Attribute");
boolean = CreateClass ("Boolean");
layout = CreateClass ("Layout");
ids = CreateClass ("Id");
ints = CreateClass ("Integer");
drawable = CreateClass ("Drawable");
strings = CreateClass ("String");
colors = CreateClass ("Color");
dimension = CreateClass ("Dimension");
raw = CreateClass ("Raw");
styleable = CreateClass ("Styleable");
style = CreateClass ("Style");
foreach (var dir in Directory.GetDirectories (resourceDirectory, "*", SearchOption.TopDirectoryOnly)) {
foreach (var file in Directory.GetFiles (dir, "*.*", SearchOption.AllDirectories)) {
ProcessResourceFile (file);
}
}
if (additionalResourceDirectories != null) {
foreach (var dir in additionalResourceDirectories) {
foreach (var file in Directory.GetFiles (dir, "*.*", SearchOption.AllDirectories)) {
ProcessResourceFile (file);
}
}
}
SortMembers (animation);
SortMembers (ids);
SortMembers (attrib);
SortMembers (arrays);
SortMembers (boolean);
SortMembers (colors);
SortMembers (dimension);
SortMembers (drawable);
SortMembers (ints);
SortMembers (layout);
SortMembers (raw);
SortMembers (strings);
SortMembers (style);
SortMembers (styleable);
if (animation.Members.Count > 1)
resources.Members.Add (animation);
if (arrays.Members.Count > 1)
resources.Members.Add (arrays);
if (attrib.Members.Count > 1)
resources.Members.Add (attrib);
if (boolean.Members.Count > 1)
resources.Members.Add (boolean);
if (colors.Members.Count > 1)
resources.Members.Add (colors);
if (dimension.Members.Count > 1)
resources.Members.Add (dimension);
if (drawable.Members.Count > 1)
resources.Members.Add (drawable);
if (ids.Members.Count > 1)
resources.Members.Add (ids);
if (ints.Members.Count > 1)
resources.Members.Add (ints);
if (layout.Members.Count > 1)
resources.Members.Add (layout);
if (raw.Members.Count > 1)
resources.Members.Add (raw);
if (strings.Members.Count > 1)
resources.Members.Add (strings);
if (style.Members.Count > 1)
resources.Members.Add (style);
if (styleable.Members.Count > 1)
resources.Members.Add (styleable);
return resources;
}
void ProcessResourceFile (string file)
{
var fileName = Path.GetFileNameWithoutExtension (file);
if (string.IsNullOrEmpty (fileName))
return;
if (fileName.EndsWith (".9", StringComparison.OrdinalIgnoreCase))
fileName = Path.GetFileNameWithoutExtension (fileName);
var path = Directory.GetParent (file).Name;
var ext = Path.GetExtension (file);
switch (ext) {
case ".xml":
case ".axml":
if (string.Compare (path, "raw", StringComparison.OrdinalIgnoreCase) == 0)
goto default;
ProcessXmlFile (file);
break;
default:
break;
}
CreateResourceField (path, fileName);
}
CodeTypeDeclaration CreateResourceClass ()
{
var decl = new CodeTypeDeclaration ("Resource") {
IsPartial = true,
};
var asm = Assembly.GetExecutingAssembly ().GetName ();
var codeAttrDecl =
new CodeAttributeDeclaration ("System.CodeDom.Compiler.GeneratedCodeAttribute",
new CodeAttributeArgument (
new CodePrimitiveExpression (asm.Name)),
new CodeAttributeArgument (
new CodePrimitiveExpression (asm.Version.ToString ()))
);
decl.CustomAttributes.Add (codeAttrDecl);
return decl;
}
CodeTypeDeclaration CreateClass (string type)
{
var t = new CodeTypeDeclaration (ResourceParser.GetNestedTypeName (type)) {
IsPartial = true,
TypeAttributes = TypeAttributes.Public,
};
t.Members.Add (new CodeConstructor () {
Attributes = MemberAttributes.Private,
});
return t;
}
void CreateField (CodeTypeDeclaration parentType, string name, Type type)
{
var f = new CodeMemberField (type, name) {
// pity I can't make the member readonly...
Attributes = MemberAttributes.Public | MemberAttributes.Static,
};
parentType.Members.Add (f);
}
void CreateIntField (CodeTypeDeclaration parentType, string name)
{
string mappedName = GetResourceName (parentType.Name, name, map);
if (parentType.Members.OfType<CodeTypeMember> ().Any (x => string.Compare (x.Name, mappedName, StringComparison.OrdinalIgnoreCase) == 0))
return;
var f = new CodeMemberField (typeof (int), mappedName) {
// pity I can't make the member readonly...
Attributes = MemberAttributes.Static | MemberAttributes.Public,
InitExpression = new CodePrimitiveExpression (0),
Comments = {
new CodeCommentStatement ("aapt resource value: 0"),
},
};
parentType.Members.Add (f);
}
void CreateIntArrayField (CodeTypeDeclaration parentType, string name, int count)
{
string mappedName = GetResourceName (parentType.Name, name, map);
if (parentType.Members.OfType<CodeTypeMember> ().Any (x => string.Compare (x.Name, mappedName, StringComparison.OrdinalIgnoreCase) == 0))
return;
var f = new CodeMemberField (typeof (int []), name) {
// pity I can't make the member readonly...
Attributes = MemberAttributes.Public | MemberAttributes.Static,
};
CodeArrayCreateExpression c = (CodeArrayCreateExpression)f.InitExpression;
if (c == null) {
f.InitExpression = c = new CodeArrayCreateExpression (typeof (int []));
}
for (int i = 0; i < count; i++)
c.Initializers.Add (new CodePrimitiveExpression (0));
parentType.Members.Add (f);
}
HashSet<string> resourceNamesToUseDirectly = new HashSet<string> () {
"integer-array",
"string-array",
"declare-styleable",
"add-resource",
};
void CreateResourceField (string root, string fieldName, XmlReader element = null)
{
var i = root.IndexOf ('-');
var item = i < 0 ? root : root.Substring (0, i);
item = resourceNamesToUseDirectly.Contains (root) ? root : item;
switch (item.ToLower ()) {
case "bool":
CreateIntField (boolean, fieldName);
break;
case "color":
CreateIntField (colors, fieldName);
break;
case "drawable":
CreateIntField (drawable, fieldName);
break;
case "dimen":
case "fraction":
CreateIntField (dimension, fieldName);
break;
case "integer":
CreateIntField (ints, fieldName);
break;
case "anim":
CreateIntField (animation, fieldName);
break;
case "attr":
CreateIntField (attrib, fieldName);
break;
case "layout":
CreateIntField (layout, fieldName);
break;
case "raw":
CreateIntField (raw, fieldName);
break;
case "string":
CreateIntField (strings, fieldName);
break;
case "enum":
case "flag":
case "id":
CreateIntField (ids, fieldName);
break;
case "integer-array":
case "string-array":
CreateIntField (arrays, fieldName);
break;
case "configVarying":
case "add-resource":
case "declare-styleable":
ProcessStyleable (element);
break;
case "style":
CreateIntField (style, fieldName.Replace (".", "_"));
break;
default:
break;
}
}
void ProcessStyleable (XmlReader reader)
{
string topName = null;
int fieldCount = 0;
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
continue;
string name = null;
if (string.IsNullOrEmpty (topName)) {
if (reader.HasAttributes) {
while (reader.MoveToNextAttribute ()) {
if (reader.Name.Replace ("android:", "") == "name")
topName = reader.Value;
}
}
}
if (!reader.IsStartElement () || reader.LocalName == "declare-styleable")
continue;
if (reader.HasAttributes) {
while (reader.MoveToNextAttribute ()) {
if (reader.Name.Replace ("android:", "") == "name")
name = reader.Value;
}
}
reader.MoveToElement ();
if (reader.LocalName == "attr") {
CreateIntField (styleable, $"{topName}_{name.Replace (":", "_")}");
if (!name.StartsWith ("android:", StringComparison.OrdinalIgnoreCase))
CreateIntField (attrib, name);
fieldCount++;
} else {
if (name != null)
CreateIntField (ids, $"{name.Replace (":", "_")}");
}
}
CreateIntArrayField (styleable, topName, fieldCount);
}
void ProcessXmlFile (string file)
{
using (var reader = XmlReader.Create (file)) {
while (reader.Read ()) {
if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
continue;
if (reader.IsStartElement ()) {
var elementName = reader.Name;
if (reader.HasAttributes) {
string name = null;
string type = null;
string id = null;
while (reader.MoveToNextAttribute ()) {
if (reader.LocalName == "name")
name = reader.Value;
if (reader.LocalName == "type")
type = reader.Value;
if (reader.LocalName == "id")
id = reader.Value.Replace ("@+id/", "").Replace ("@id/", ""); ;
}
if (name?.Contains ("android:") ?? false)
continue;
if (id?.Contains ("android:") ?? false)
continue;
// Move the reader back to the element node.
reader.MoveToElement ();
if (!string.IsNullOrEmpty (name))
CreateResourceField (type ?? elementName, name, reader.ReadSubtree ());
if (!string.IsNullOrEmpty (id)) {
CreateIntField (ids, id);
}
}
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Build.Utilities;
namespace Xamarin.Android.Tasks
{
class ResourceParser
{
public TaskLoggingHelper Log { get; set; }
internal int ToInt32 (string value, int @base)
{
try {
return Convert.ToInt32 (value, @base);
} catch (Exception e) {
throw new NotSupportedException (
string.Format ("Could not convert value '{0}' (base '{1}') into an Int32.",
value, @base),
e);
}
}
internal static string GetNestedTypeName (string name)
{
switch (name) {
case "anim": return "Animation";
case "attr": return "Attribute";
case "bool": return "Boolean";
case "dimen": return "Dimension";
default: return char.ToUpperInvariant (name[0]) + name.Substring (1);
}
}
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 mappedValue.Substring (mappedValue.LastIndexOf (Path.DirectorySeparatorChar) + 1);
}
Log.LogDebugMessage (" - Not remapping resource: {0}.{1}", type, name);
return name;
}
}
}

Просмотреть файл

@ -102,6 +102,8 @@
<Compile Include="Mono.Android\GrantUriPermissionAttribute.Partial.cs" />
<Compile Include="Utilities\InvalidActivityNameException.cs" />
<Compile Include="Utilities\JavaResourceParser.cs" />
<Compile Include="Utilities\ResourceParser.cs" />
<Compile Include="Utilities\ManagedResourceParser.cs" />
<Compile Include="Utilities\ManifestDocumentElement.cs" />
<Compile Include="Utilities\ManifestDocument.cs" />
<Compile Include="Mono.Android\MetaDataAttribute.Partial.cs" />

Просмотреть файл

@ -993,6 +993,9 @@ because xbuild doesn't support framework reference assemblies.
<_AndroidResourceDesignerFile Condition=" '$(AndroidUseIntermediateDesignerFile)' != 'True' ">$(AndroidResgenFile)</_AndroidResourceDesignerFile>
<_AndroidStaticResourcesFlag>$(IntermediateOutputPath)static.flag</_AndroidStaticResourcesFlag>
<_AndroidResourcesCacheFile>$(IntermediateOutputPath)mergeresources.cache</_AndroidResourcesCacheFile>
<AndroidUseManagedDesignTimeResourceGenerator Condition=" '$(AndroidUseManagedDesignTimeResourceGenerator)' == '' " >True</AndroidUseManagedDesignTimeResourceGenerator>
<_AndroidDesignTimeResDirIntermediate>$(IntermediateOutputPath)designtime\</_AndroidDesignTimeResDirIntermediate>
<_AndroidManagedResourceDesignerFile>$(_AndroidDesignTimeResDirIntermediate)$(_AndroidResourceDesigner)</_AndroidManagedResourceDesignerFile>
</PropertyGroup>
<ItemGroup>
@ -1036,11 +1039,60 @@ because xbuild doesn't support framework reference assemblies.
Overwrite="true"/>
</Target>
<Choose>
<When Condition=" $(AndroidUseManagedDesignTimeResourceGenerator) == 'True' And '$(DesignTimeBuild)' == 'True' And '$(BuildingInsideVisualStudio)' == 'True' ">
<PropertyGroup>
<ManagedDesignTimeBuild>True</ManagedDesignTimeBuild>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<ManagedDesignTimeBuild>False</ManagedDesignTimeBuild>
</PropertyGroup>
</Otherwise>
</Choose>
<!-- Managed DesignTime Resource Generation -->
<Target Name="_ManagedUpdateAndroidResgen" Condition=" '$(ManagedDesignTimeBuild)' == 'True' "
Inputs="@(AndroidResource);@(ReferencePath)"
Outputs="$(_AndroidManagedResourceDesignerFile)"
DependsOnTargets="_ExtractLibraryProjectImports">
<MakeDir Directories="$(_AndroidDesignTimeResDirIntermediate)" />
<!-- Parse primary R.java and create Resources.Designer.cs -->
<GenerateResourceDesigner
ContinueOnError="$(DesignTimeBuild)"
NetResgenOutputFile="$(_AndroidManagedResourceDesignerFile)"
JavaResgenInputFile="$(_GeneratedPrimaryJavaResgenFile)"
Namespace="$(AndroidResgenNamespace)"
ProjectDir="$(ProjectDir)"
Resources="@(AndroidResource)"
ResourceDirectory="$(MonoAndroidResourcePrefix)"
AdditionalResourceDirectories="@(LibraryResourceDirectories)"
IsApplication="$(AndroidApplication)"
References="@(ReferencePath)"
UseManagedResourceGenerator="True"
/>
<ItemGroup>
<CorrectCasedItem Include="%(Compile.Identity)" Condition="'%(Compile.Identity)' == '$(AndroidResgenFile)'"/>
<CorrectCasedItem Include="%(Compile.Identity)" Condition="'%(Compile.Identity)' == 'Resources\Resource.designer.cs'"/>
<Compile Remove="@(CorrectCasedItem)" Condition=" '$(ManagedDesignTimeBuild)' == 'True' And '%(CorrectCasedItem.Identity)' != '' "/>
<Compile Include="$(_AndroidManagedResourceDesignerFile)" Condition=" '$(ManagedDesignTimeBuild)' == 'True' And Exists ('$(_AndroidManagedResourceDesignerFile)')" />
</ItemGroup>
<WriteLinesToFile
Condition="Exists ('$(_AndroidManagedResourceDesignerFile)')"
File="$(IntermediateOutputPath)$(CleanFile)"
Lines="$(_AndroidManagedResourceDesignerFile)"
Overwrite="false" />
</Target>
<!-- Resource Build -->
<Target Name="UpdateAndroidResources"
DependsOnTargets="$(CoreResolveReferencesDependsOn);_CreatePropertiesCache;_CheckForDeletedResourceFile;_ComputeAndroidResourcePaths;_UpdateAndroidResgen;_AddLibraryProjectsEmbeddedResourceToProject;_GenerateJavaDesignerForComponent" />
<Target Name="_UpdateAndroidResources" Condition=" '$(ManagedDesignTimeBuild)' == 'False' "
DependsOnTargets="$(CoreResolveReferencesDependsOn);_CreatePropertiesCache;_CheckForDeletedResourceFile;_ComputeAndroidResourcePaths;_UpdateAndroidResgen;_AddLibraryProjectsEmbeddedResourceToProject;_GenerateJavaDesignerForComponent">
</Target>
<Target Name="UpdateAndroidResources" DependsOnTargets="_ManagedUpdateAndroidResgen;_UpdateAndroidResources" />
<!-- Handle a case where the designer file has been deleted, but the flag file still exists -->
<Target Name="_CheckForDeletedResourceFile">
<Delete Files="$(_AndroidResgenFlagFile)"
@ -1348,6 +1400,7 @@ because xbuild doesn't support framework reference assemblies.
AdditionalResourceDirectories="@(LibraryResourceDirectories)"
IsApplication="$(AndroidApplication)"
References="@(ReferencePath)"
UseManagedResourceGenerator="False"
/>
<!-- Only copy if the file contents changed, so users only get Reload? dialog for real changes -->
@ -2473,6 +2526,7 @@ because xbuild doesn't support framework reference assemblies.
<RemoveDirFixed Directories="$(MonoAndroidIntermediate)proguard" Condition="Exists ('$(MonoAndroidIntermediate)proguard')" />
<RemoveDirFixed Directories="$(MonoAndroidIntermediateResourceCache)" Condition="Exists ('$(MonoAndroidIntermediateResourceCache)')" />
<RemoveDirFixed Directories="$(_AndroidAotBinDirectory)" Condition="Exists ('$(_AndroidAotBinDirectory)')" />
<RemoveDirFixed Directories="$(_AndroidDesignTimeResDirIntermediate)" Condition="Exists ('$(_AndroidDesignTimeResDirIntermediate)')" />
<Delete Files="$(IntermediateOutputPath)_dex_stamp" />
<Delete Files="$(MonoAndroidIntermediate)R.cs.flag" />
<Delete Files="$(MonoAndroidIntermediate)acw-map.txt" />