Skip to content

Commit

Permalink
rework a bit
Browse files Browse the repository at this point in the history
  • Loading branch information
dellis1972 committed Apr 27, 2022
1 parent 4ff47c6 commit f32fd8b
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ Copyright (C) 2016 Xamarin. All rights reserved.
<_GenerateResourceCaseMapFile>$(_DesignerIntermediateOutputPath)case_map.txt</_GenerateResourceCaseMapFile>
</PropertyGroup>

<Target Name="_GenerateRtxt"
Inputs="@(_AndroidResourceDest);@(LibraryResourceDirectories->'%(StampFile)')"
Outputs="$(_DesignerIntermediateOutputPath)R.txt"
>
<!-- Generate an R.txt file using the Managed Parser -->
</Target>

<Target Name="_GenerateResourceCaseMap"
DependsOnTargets="_ComputeAndroidResourcePaths"
Inputs="@(_AndroidResourceDest);@(LibraryResourceDirectories->'%(StampFile)')"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,13 @@ bool Run(DirectoryAssemblyResolver res) {
if (File.Exists (RTxtFile.ItemSpec)) {
var parser = new RtxtParser ();
var resources = parser.Parse (RTxtFile.ItemSpec, Log, resource_fixup);
foreach (var resource in resources) {
var r = resource.Value;
foreach (var r in resources) {
switch (r.Type) {
case RType.Integer:
CreateIntProperty (r.ResourceType, r.Identifier, r.Id, resourceDesigner, module);
CreateIntProperty (r.ResourceTypeName, r.Identifier, r.Id, resourceDesigner, module);
break;
case RType.Array:
CreateIntArrayProperty (r.ResourceType, r.Identifier, r.Ids, resourceDesigner, module);
CreateIntArrayProperty (r.ResourceTypeName, r.Identifier, r.Ids, resourceDesigner, module);
break;
}
}
Expand Down
45 changes: 45 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (C) 2022 Microsoft Ltd, Inc. All rights reserved.
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Android.Build.Tasks;

namespace Xamarin.Android.Tasks
{
public class GenerateRtxt : AndroidTask
{
public override string TaskPrefix => "GR";

public string RTxtFile { get; set; }

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

public string[] AdditionalResourceDirectories { get; set; }

public string JavaPlatformJarPath { get; set; }

public string CaseMapFile { get; set; }

public override bool RunTask ()
{
// Parse the Resource files and then generate an R.txt file
var writer = new RtxtWriter ();

var resource_fixup = MonoAndroidHelper.LoadMapFile (BuildEngine4, CaseMapFile, StringComparer.OrdinalIgnoreCase);

var javaPlatformDirectory = Path.GetDirectoryName (JavaPlatformJarPath);
var parser = new FileResourceParser () { Log = Log, JavaPlatformDirectory = javaPlatformDirectory, ResourceFlagFile = ResourceFlagFile};
var resources = parser.Parse (ResourceDirectory, AdditionalResourceDirectories, resource_fixup);

writer.Write (RTxtFile, resources);

return !Log.HasLoggedErrors;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ void Extract (
string importsDir = Path.Combine (outDirForDll, ImportsDirectory);
string resDir = Path.Combine (importsDir, "res");
string resDirArchive = Path.Combine (resDir, "..", "res.zip");
string rTxt = Path.Combine (importsDir, "R.txt");
string assetsDir = Path.Combine (importsDir, "assets");

bool updated = false;
Expand All @@ -358,7 +359,7 @@ void Extract (
AddJar (jars, Path.GetFullPath (file));
}
}
if (Directory.Exists (resDir)) {
if (Directory.Exists (resDir) || File.Exists (rTxt)) {
var skipProcessing = aarFile.GetMetadata (AndroidSkipResourceProcessing);
if (string.IsNullOrEmpty (skipProcessing)) {
skipProcessing = "True";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,30 @@ public void UpdateLayoutIdIsIncludedInDesigner ([Values(true, false)] bool useRt
Directory.Delete (Path.Combine (Root, path), recursive: true);
}

[Test]
[Category ("SmokeTests")]
public void RtxtGeneratorOutput ()
{
var path = Path.Combine ("temp", TestName);
int platform = AndroidSdkResolver.GetMaxInstalledPlatform ();
string resPath = Path.Combine (Root, path, "res");
string rTxt = Path.Combine (Root, path, "R.txt");
CreateResourceDirectory (path);
List<BuildErrorEventArgs> errors = new List<BuildErrorEventArgs> ();
List<BuildMessageEventArgs> messages = new List<BuildMessageEventArgs> ();
IBuildEngine engine = new MockBuildEngine (TestContext.Out, errors: errors, messages: messages);
var generateRtxt = new GenerateRtxt () {
BuildEngine = engine,
RtxtFile = rTxt,
ResourceDirectory = resPath,
JavaPlatformJarPath = Path.Combine (AndroidSdkDirectory, "platforms", $"android-{platform}", "android.jar"),
};
Assert.IsTrue (generateRtxt.Execute (), "Task should have succeeded.");
FileAssert.Exists (rTxt, $"{rTxt} should have been created.");

Directory.Delete (Path.Combine (Root, path), recursive: true);
}

[Test]
[Category ("SmokeTests")]
public void CompareAapt2AndManagedParserOutput ()
Expand Down
219 changes: 219 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Build.Utilities;
using Microsoft.Android.Build.Tasks;

namespace Xamarin.Android.Tasks
{
class FileResourceParser : ResourceParser
{
Dictionary<R, R []> arrayMapping = new Dictionary<R, R []> ();
public Dictionary<string, R> Parse (string resourceDirectory, IEnumerable<string> additionalResourceDirectories, Dictionary<string, string> resourceMap)
{
Log.LogDebugMessage ($"Processing Directory {resourceDirectory}");
var result = new Dictionary<string, R> ();
Dictionary<string, SortedSet<R>> resources = new Dictionary<string, SortedSet<R>> ();
foreach (var dir in Directory.EnumerateDirectories (resourceDirectory, "*", SearchOption.TopDirectoryOnly)) {
foreach (var file in Directory.EnumerateFiles (dir, "*.*", SearchOption.AllDirectories)) {
ProcessResourceFile (file, resources);
}
}
if (additionalResourceDirectories != null) {
foreach (var dir in additionalResourceDirectories) {
Log.LogDebugMessage ($"Processing Directory {dir}");
if (Directory.Exists (dir)) {
foreach (var file in Directory.EnumerateFiles (dir, "*.*", SearchOption.AllDirectories)) {
ProcessResourceFile (file, resources);
}
} else {
Log.LogDebugMessage ($"Skipping non-existent directory: {dir}");
}
}
}
return result;
}

void ProcessResourceFile (string file, Dictionary<string, SortedSet<R>> resources)
{
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;
try {
ProcessXmlFile (file, resources);
} catch (XmlException ex) {
Log.LogCodedWarning ("XA1000", Properties.Resources.XA1000, file, ex);
}
break;
default:
break;
}
if (!resources.ContainsKey (path))
resources[path] = new SortedSet<R>();
var r = new R () {
ResourceTypeName = path,
Identifier = fileName,
Id = -1,
};
resources[path].Add (r);
}

void ProcessStyleable (XmlReader reader, Dictionary<string, SortedSet<R>> resources)
{
string topName = null;
int fieldCount = 0;
List<R> fields = new List<R> ();
List<string> attribs = new List<string> ();
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") {
attribs.Add (name);
} else {
if (name != null) {
var r = new R () {
ResourceTypeName = "id",
Identifier = name,
Id = -1,
};
resources [r.ResourceTypeName].Add (r);
}
}
}
var field = new R () {
ResourceTypeName = "styleable",
Identifier = topName,
Type = RType.Array,
};
if (!arrayMapping.ContainsKey (field)) {
attribs.Sort (StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < attribs.Count; i++) {
string name = attribs [i];
if (!name.StartsWith ("android:", StringComparison.OrdinalIgnoreCase)) {
var r = new R () {
ResourceTypeName = "attrib",
Identifier = name,
Id = -1,
};
resources [r.ResourceTypeName].Add (r);
fields.Add (r);
} else {
// this is an android:xxx resource, we should not calculate the id
// we should get it from "somewhere" maybe the pubic.xml
var r = new R () {
ResourceTypeName = "attrib",
Identifier = name,
Id = 0,
};
fields.Add (r);
}
}
if (field.Type != RType.Array)
return;
arrayMapping.Add (field, fields.ToArray ());
}
}

void ProcessXmlFile (string file, Dictionary<string, SortedSet<R>> resources)
{
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;
string custom_id = null;
while (reader.MoveToNextAttribute ()) {
if (reader.LocalName == "name")
name = reader.Value;
if (reader.LocalName == "type")
type = reader.Value;
if (reader.LocalName == "id") {
string[] values = reader.Value.Split ('/');
if (values.Length != 2) {
id = reader.Value.Replace ("@+id/", "").Replace ("@id/", "");
} else {
if (values [0] != "@+id" && values [0] != "@id" && !values [0].Contains ("android:")) {
custom_id = values [0].Replace ("@", "").Replace ("+", "");
}
id = values [1];
}

}
if (reader.LocalName == "inflatedId") {
string inflateId = reader.Value.Replace ("@+id/", "").Replace ("@id/", "");
var r = new R () {
ResourceTypeName = "id",
Identifier = inflateId,
Id = -1,
};
resources[r.ResourceTypeName].Add (r);
}
}
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 (custom_id) && !custom_types.TryGetValue (custom_id, out customClass)) {
//customClass = CreateClass (custom_id);
//custom_types.Add (custom_id, customClass);
//}
if (!string.IsNullOrEmpty (id)) {
//CreateIntField (customClass ?? ids, id);
var r = new R () {
ResourceTypeName = custom_id ?? "id",
Identifier = id,
Id = -1,
};
resources[r.ResourceTypeName].Add (r);
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Xamarin.Android.Tasks
{
class ManagedResourceParser : ResourceParser
class ManagedResourceParser : FileResourceParser
{
class CompareTuple : IComparer<(int Key, CodeMemberField Value)>
{
Expand Down Expand Up @@ -298,9 +298,8 @@ void ProcessRtxtFile (string file)
{
var parser = new RtxtParser ();
var resources = parser.Parse (file, Log, map);
foreach (var resource in resources) {
var r = resource.Value;
var cl = CreateClass (r.ResourceType);
foreach (var r in resources) {
var cl = CreateClass (r.ResourceTypeName);
switch (r.Type) {
case RType.Integer:
CreateIntField (cl, r.Identifier, r.Id);
Expand Down
Loading

0 comments on commit f32fd8b

Please sign in to comment.