Skip to content

Commit d702b62

Browse files
committed
rework a bit
1 parent a2705db commit d702b62

File tree

8 files changed

+373
-72
lines changed

8 files changed

+373
-72
lines changed

src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ Copyright (C) 2016 Xamarin. All rights reserved.
3030
<_GenerateResourceCaseMapFile>$(_DesignerIntermediateOutputPath)case_map.txt</_GenerateResourceCaseMapFile>
3131
</PropertyGroup>
3232

33+
<Target Name="_GenerateRtxt"
34+
Inputs="@(_AndroidResourceDest);@(LibraryResourceDirectories->'%(StampFile)')"
35+
Outputs="$(_DesignerIntermediateOutputPath)R.txt"
36+
>
37+
<!-- Generate an R.txt file using the Managed Parser -->
38+
</Target>
39+
3340
<Target Name="_GenerateResourceCaseMap"
3441
DependsOnTargets="_ComputeAndroidResourcePaths"
3542
Inputs="@(_AndroidResourceDest);@(LibraryResourceDirectories->'%(StampFile)')"

src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerAssembly.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,13 @@ bool Run(DirectoryAssemblyResolver res) {
127127
if (File.Exists (RTxtFile.ItemSpec)) {
128128
var parser = new RtxtParser ();
129129
var resources = parser.Parse (RTxtFile.ItemSpec, Log, resource_fixup);
130-
foreach (var resource in resources) {
131-
var r = resource.Value;
130+
foreach (var r in resources) {
132131
switch (r.Type) {
133132
case RType.Integer:
134-
CreateIntProperty (r.ResourceType, r.Identifier, r.Id, resourceDesigner, module);
133+
CreateIntProperty (r.ResourceTypeName, r.Identifier, r.Id, resourceDesigner, module);
135134
break;
136135
case RType.Array:
137-
CreateIntArrayProperty (r.ResourceType, r.Identifier, r.Ids, resourceDesigner, module);
136+
CreateIntArrayProperty (r.ResourceTypeName, r.Identifier, r.Ids, resourceDesigner, module);
138137
break;
139138
}
140139
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (C) 2022 Microsoft Ltd, Inc. All rights reserved.
2+
using System;
3+
using System.CodeDom;
4+
using System.CodeDom.Compiler;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using Microsoft.Build.Framework;
9+
using Microsoft.Build.Utilities;
10+
using Microsoft.Android.Build.Tasks;
11+
12+
namespace Xamarin.Android.Tasks
13+
{
14+
public class GenerateRtxt : AndroidTask
15+
{
16+
public override string TaskPrefix => "GR";
17+
18+
public string RTxtFile { get; set; }
19+
20+
[Required]
21+
public string ResourceDirectory { get; set; }
22+
23+
public string[] AdditionalResourceDirectories { get; set; }
24+
25+
public string JavaPlatformJarPath { get; set; }
26+
27+
public string CaseMapFile { get; set; }
28+
29+
public override bool RunTask ()
30+
{
31+
// Parse the Resource files and then generate an R.txt file
32+
var writer = new RtxtWriter ();
33+
34+
var resource_fixup = MonoAndroidHelper.LoadMapFile (BuildEngine4, CaseMapFile, StringComparer.OrdinalIgnoreCase);
35+
36+
var javaPlatformDirectory = Path.GetDirectoryName (JavaPlatformJarPath);
37+
var parser = new FileResourceParser () { Log = Log, JavaPlatformDirectory = javaPlatformDirectory, ResourceFlagFile = ResourceFlagFile};
38+
var resources = parser.Parse (ResourceDirectory, AdditionalResourceDirectories, resource_fixup);
39+
40+
writer.Write (RTxtFile, resources);
41+
42+
return !Log.HasLoggedErrors;
43+
}
44+
}
45+
}

src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ void Extract (
344344
string importsDir = Path.Combine (outDirForDll, ImportsDirectory);
345345
string resDir = Path.Combine (importsDir, "res");
346346
string resDirArchive = Path.Combine (resDir, "..", "res.zip");
347+
string rTxt = Path.Combine (importsDir, "R.txt");
347348
string assetsDir = Path.Combine (importsDir, "assets");
348349

349350
bool updated = false;
@@ -358,7 +359,7 @@ void Extract (
358359
AddJar (jars, Path.GetFullPath (file));
359360
}
360361
}
361-
if (Directory.Exists (resDir)) {
362+
if (Directory.Exists (resDir) || File.Exists (rTxt)) {
362363
var skipProcessing = aarFile.GetMetadata (AndroidSkipResourceProcessing);
363364
if (string.IsNullOrEmpty (skipProcessing)) {
364365
skipProcessing = "True";

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,30 @@ public void UpdateLayoutIdIsIncludedInDesigner ([Values(true, false)] bool useRt
471471
Directory.Delete (Path.Combine (Root, path), recursive: true);
472472
}
473473

474+
[Test]
475+
[Category ("SmokeTests")]
476+
public void RtxtGeneratorOutput ()
477+
{
478+
var path = Path.Combine ("temp", TestName);
479+
int platform = AndroidSdkResolver.GetMaxInstalledPlatform ();
480+
string resPath = Path.Combine (Root, path, "res");
481+
string rTxt = Path.Combine (Root, path, "R.txt");
482+
CreateResourceDirectory (path);
483+
List<BuildErrorEventArgs> errors = new List<BuildErrorEventArgs> ();
484+
List<BuildMessageEventArgs> messages = new List<BuildMessageEventArgs> ();
485+
IBuildEngine engine = new MockBuildEngine (TestContext.Out, errors: errors, messages: messages);
486+
var generateRtxt = new GenerateRtxt () {
487+
BuildEngine = engine,
488+
RtxtFile = rTxt,
489+
ResourceDirectory = resPath,
490+
JavaPlatformJarPath = Path.Combine (AndroidSdkDirectory, "platforms", $"android-{platform}", "android.jar"),
491+
};
492+
Assert.IsTrue (generateRtxt.Execute (), "Task should have succeeded.");
493+
FileAssert.Exists (rTxt, $"{rTxt} should have been created.");
494+
495+
Directory.Delete (Path.Combine (Root, path), recursive: true);
496+
}
497+
474498
[Test]
475499
[Category ("SmokeTests")]
476500
public void CompareAapt2AndManagedParserOutput ()
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
using System;
2+
using System.CodeDom;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Reflection;
7+
using System.Xml;
8+
using System.Xml.Linq;
9+
using System.Xml.XPath;
10+
using System.Text;
11+
using System.Text.RegularExpressions;
12+
using Microsoft.Build.Utilities;
13+
using Microsoft.Android.Build.Tasks;
14+
15+
namespace Xamarin.Android.Tasks
16+
{
17+
class FileResourceParser : ResourceParser
18+
{
19+
Dictionary<R, R []> arrayMapping = new Dictionary<R, R []> ();
20+
public Dictionary<string, R> Parse (string resourceDirectory, IEnumerable<string> additionalResourceDirectories, Dictionary<string, string> resourceMap)
21+
{
22+
Log.LogDebugMessage ($"Processing Directory {resourceDirectory}");
23+
var result = new Dictionary<string, R> ();
24+
Dictionary<string, SortedSet<R>> resources = new Dictionary<string, SortedSet<R>> ();
25+
foreach (var dir in Directory.EnumerateDirectories (resourceDirectory, "*", SearchOption.TopDirectoryOnly)) {
26+
foreach (var file in Directory.EnumerateFiles (dir, "*.*", SearchOption.AllDirectories)) {
27+
ProcessResourceFile (file, resources);
28+
}
29+
}
30+
if (additionalResourceDirectories != null) {
31+
foreach (var dir in additionalResourceDirectories) {
32+
Log.LogDebugMessage ($"Processing Directory {dir}");
33+
if (Directory.Exists (dir)) {
34+
foreach (var file in Directory.EnumerateFiles (dir, "*.*", SearchOption.AllDirectories)) {
35+
ProcessResourceFile (file, resources);
36+
}
37+
} else {
38+
Log.LogDebugMessage ($"Skipping non-existent directory: {dir}");
39+
}
40+
}
41+
}
42+
return result;
43+
}
44+
45+
void ProcessResourceFile (string file, Dictionary<string, SortedSet<R>> resources)
46+
{
47+
var fileName = Path.GetFileNameWithoutExtension (file);
48+
if (string.IsNullOrEmpty (fileName))
49+
return;
50+
if (fileName.EndsWith (".9", StringComparison.OrdinalIgnoreCase))
51+
fileName = Path.GetFileNameWithoutExtension (fileName);
52+
var path = Directory.GetParent (file).Name;
53+
var ext = Path.GetExtension (file);
54+
switch (ext) {
55+
case ".xml":
56+
case ".axml":
57+
if (string.Compare (path, "raw", StringComparison.OrdinalIgnoreCase) == 0)
58+
goto default;
59+
try {
60+
ProcessXmlFile (file, resources);
61+
} catch (XmlException ex) {
62+
Log.LogCodedWarning ("XA1000", Properties.Resources.XA1000, file, ex);
63+
}
64+
break;
65+
default:
66+
break;
67+
}
68+
if (!resources.ContainsKey (path))
69+
resources[path] = new SortedSet<R>();
70+
var r = new R () {
71+
ResourceTypeName = path,
72+
Identifier = fileName,
73+
Id = -1,
74+
};
75+
resources[path].Add (r);
76+
}
77+
78+
void ProcessStyleable (XmlReader reader, Dictionary<string, SortedSet<R>> resources)
79+
{
80+
string topName = null;
81+
int fieldCount = 0;
82+
List<R> fields = new List<R> ();
83+
List<string> attribs = new List<string> ();
84+
while (reader.Read ()) {
85+
if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
86+
continue;
87+
string name = null;
88+
if (string.IsNullOrEmpty (topName)) {
89+
if (reader.HasAttributes) {
90+
while (reader.MoveToNextAttribute ()) {
91+
if (reader.Name.Replace ("android:", "") == "name")
92+
topName = reader.Value;
93+
}
94+
}
95+
}
96+
if (!reader.IsStartElement () || reader.LocalName == "declare-styleable")
97+
continue;
98+
if (reader.HasAttributes) {
99+
while (reader.MoveToNextAttribute ()) {
100+
if (reader.Name.Replace ("android:", "") == "name")
101+
name = reader.Value;
102+
}
103+
}
104+
reader.MoveToElement ();
105+
if (reader.LocalName == "attr") {
106+
attribs.Add (name);
107+
} else {
108+
if (name != null) {
109+
var r = new R () {
110+
ResourceTypeName = "id",
111+
Identifier = name,
112+
Id = -1,
113+
};
114+
resources [r.ResourceTypeName].Add (r);
115+
}
116+
}
117+
}
118+
var field = new R () {
119+
ResourceTypeName = "styleable",
120+
Identifier = topName,
121+
Type = RType.Array,
122+
};
123+
if (!arrayMapping.ContainsKey (field)) {
124+
attribs.Sort (StringComparer.OrdinalIgnoreCase);
125+
for (int i = 0; i < attribs.Count; i++) {
126+
string name = attribs [i];
127+
if (!name.StartsWith ("android:", StringComparison.OrdinalIgnoreCase)) {
128+
var r = new R () {
129+
ResourceTypeName = "attrib",
130+
Identifier = name,
131+
Id = -1,
132+
};
133+
resources [r.ResourceTypeName].Add (r);
134+
fields.Add (r);
135+
} else {
136+
// this is an android:xxx resource, we should not calculate the id
137+
// we should get it from "somewhere" maybe the pubic.xml
138+
var r = new R () {
139+
ResourceTypeName = "attrib",
140+
Identifier = name,
141+
Id = 0,
142+
};
143+
fields.Add (r);
144+
}
145+
}
146+
if (field.Type != RType.Array)
147+
return;
148+
arrayMapping.Add (field, fields.ToArray ());
149+
}
150+
}
151+
152+
void ProcessXmlFile (string file, Dictionary<string, SortedSet<R>> resources)
153+
{
154+
using (var reader = XmlReader.Create (file)) {
155+
while (reader.Read ()) {
156+
if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
157+
continue;
158+
if (reader.IsStartElement ()) {
159+
var elementName = reader.Name;
160+
if (reader.HasAttributes) {
161+
string name = null;
162+
string type = null;
163+
string id = null;
164+
string custom_id = null;
165+
while (reader.MoveToNextAttribute ()) {
166+
if (reader.LocalName == "name")
167+
name = reader.Value;
168+
if (reader.LocalName == "type")
169+
type = reader.Value;
170+
if (reader.LocalName == "id") {
171+
string[] values = reader.Value.Split ('/');
172+
if (values.Length != 2) {
173+
id = reader.Value.Replace ("@+id/", "").Replace ("@id/", "");
174+
} else {
175+
if (values [0] != "@+id" && values [0] != "@id" && !values [0].Contains ("android:")) {
176+
custom_id = values [0].Replace ("@", "").Replace ("+", "");
177+
}
178+
id = values [1];
179+
}
180+
181+
}
182+
if (reader.LocalName == "inflatedId") {
183+
string inflateId = reader.Value.Replace ("@+id/", "").Replace ("@id/", "");
184+
var r = new R () {
185+
ResourceTypeName = "id",
186+
Identifier = inflateId,
187+
Id = -1,
188+
};
189+
resources[r.ResourceTypeName].Add (r);
190+
}
191+
}
192+
if (name?.Contains ("android:") ?? false)
193+
continue;
194+
if (id?.Contains ("android:") ?? false)
195+
continue;
196+
// Move the reader back to the element node.
197+
reader.MoveToElement ();
198+
//if (!string.IsNullOrEmpty (name))
199+
//CreateResourceField (type ?? elementName, name, reader.ReadSubtree ());
200+
//if (!string.IsNullOrEmpty (custom_id) && !custom_types.TryGetValue (custom_id, out customClass)) {
201+
//customClass = CreateClass (custom_id);
202+
//custom_types.Add (custom_id, customClass);
203+
//}
204+
if (!string.IsNullOrEmpty (id)) {
205+
//CreateIntField (customClass ?? ids, id);
206+
var r = new R () {
207+
ResourceTypeName = custom_id ?? "id",
208+
Identifier = id,
209+
Id = -1,
210+
};
211+
resources[r.ResourceTypeName].Add (r);
212+
}
213+
}
214+
}
215+
}
216+
}
217+
}
218+
}
219+
}

src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace Xamarin.Android.Tasks
1616
{
17-
class ManagedResourceParser : ResourceParser
17+
class ManagedResourceParser : FileResourceParser
1818
{
1919
class CompareTuple : IComparer<(int Key, CodeMemberField Value)>
2020
{
@@ -298,9 +298,8 @@ void ProcessRtxtFile (string file)
298298
{
299299
var parser = new RtxtParser ();
300300
var resources = parser.Parse (file, Log, map);
301-
foreach (var resource in resources) {
302-
var r = resource.Value;
303-
var cl = CreateClass (r.ResourceType);
301+
foreach (var r in resources) {
302+
var cl = CreateClass (r.ResourceTypeName);
304303
switch (r.Type) {
305304
case RType.Integer:
306305
CreateIntField (cl, r.Identifier, r.Id);

0 commit comments

Comments
 (0)