Skip to content

Commit d544aca

Browse files
committed
Generate mappings used by Xamarin.Android.Build.Tasks.
1 parent bde026f commit d544aca

File tree

44 files changed

+1615
-1134
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1615
-1134
lines changed

build-tools/manifest-attribute-codegen/Extensions/StringExtensions.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using System.Text;
23
using System.Xml.Linq;
34
using Xamarin.SourceWriter;
@@ -41,19 +42,6 @@ public static string ToActualName (this string s)
4142
return ret.Length == 0 ? "manifest" : ret;
4243
}
4344

44-
public static bool? GetAsBoolOrNull (this XElement element, string attribute)
45-
{
46-
var value = element.Attribute (attribute)?.Value;
47-
48-
if (value is null)
49-
return null;
50-
51-
if (bool.TryParse (value, out var ret))
52-
return ret;
53-
54-
return null;
55-
}
56-
5745
public static bool GetAttributeBoolOrDefault (this XElement element, string attribute, bool defaultValue)
5846
{
5947
var value = element.Attribute (attribute)?.Value;
@@ -117,4 +105,38 @@ public static void WriteAutoGeneratedHeader (this CodeWriter sw)
117105
sw.WriteLine ();
118106
sw.WriteLine ("#nullable enable"); // Roslyn turns off NRT for generated files by default, re-enable it
119107
}
108+
109+
/// <summary>
110+
/// Returns the first subset of a delimited string. ("127.0.0.1" -> "127")
111+
/// </summary>
112+
[return: NotNullIfNotNull (nameof (s))]
113+
public static string? FirstSubset (this string? s, char separator)
114+
{
115+
if (!s.HasValue ())
116+
return s;
117+
118+
var index = s.IndexOf (separator);
119+
120+
if (index < 0)
121+
return s;
122+
123+
return s.Substring (0, index);
124+
}
125+
126+
/// <summary>
127+
/// Returns the final subset of a delimited string. ("127.0.0.1" -> "1")
128+
/// </summary>
129+
[return: NotNullIfNotNull (nameof (s))]
130+
public static string? LastSubset (this string? s, char separator)
131+
{
132+
if (!s.HasValue ())
133+
return s;
134+
135+
var index = s.LastIndexOf (separator);
136+
137+
if (index < 0)
138+
return s;
139+
140+
return s.Substring (index + 1);
141+
}
120142
}

build-tools/manifest-attribute-codegen/Models/AttributeDefinition.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ public AttributeDefinition (string apiLevel, string name, string format)
1717
Format = format;
1818
}
1919

20+
public string GetAttributeType ()
21+
{
22+
return Format switch {
23+
"boolean" => "bool",
24+
"integer" => "int",
25+
"string" => "string?",
26+
_ => "string?",
27+
};
28+
}
29+
2030
public static AttributeDefinition FromElement (string api, XElement e)
2131
{
2232
var name = e.GetAttributeStringOrEmpty ("name");

build-tools/manifest-attribute-codegen/Models/MetadataSource.cs

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ class MetadataSource
1010

1111
static readonly MetadataElement default_element = new MetadataElement ("*");
1212

13-
1413
public MetadataSource (string filename)
1514
{
1615
var xml = XElement.Load (filename);
1716

1817
foreach (var element in xml.Elements ("element")) {
1918
var path = element.Attribute ("path")?.Value ?? throw new InvalidDataException ("Missing 'path' attribute.");
2019

20+
if (!path.Contains ('.'))
21+
throw new InvalidDataException ($"Invalid 'path' attribute value: {path}");
22+
2123
Elements.Add (path, new MetadataElement (path) {
22-
Visible = element.GetAsBoolOrNull ("visible"),
24+
Visible = element.GetAttributeBoolOrDefault ("visible", true),
2325
Type = element.Attribute ("type")?.Value,
2426
Name = element.Attribute ("name")?.Value,
2527
Obsolete = element.Attribute ("obsolete")?.Value,
26-
ReadOnly = element.GetAsBoolOrNull ("readonly") ?? false,
28+
ReadOnly = element.GetAttributeBoolOrDefault("readonly", false),
29+
ManualMap = element.GetAttributeBoolOrDefault ("manualMap", false),
2730
});
2831
}
2932

@@ -35,63 +38,85 @@ public MetadataSource (string filename)
3538

3639
public MetadataElement GetMetadata (string path)
3740
{
38-
if (Elements.TryGetValue (path, out var element)) {
39-
element.Consumed = true;
41+
if (Elements.TryGetValue (path, out var element))
4042
return element;
41-
}
4243

4344
return default_element;
4445
}
4546

46-
public void EnsureMetadataElementsConsumed ()
47+
public void EnsureAllElementsAccountedFor (List<ElementDefinition> elements)
4748
{
48-
var unconsumed = Elements.Values.Where (e => !e.Consumed).ToList ();
49-
50-
if (unconsumed.Count == 0)
51-
return;
49+
var missing = new List<string> ();
5250

53-
var sb = new StringBuilder ();
54-
sb.AppendLine ("The following metadata elements were not consumed:");
51+
foreach (var e in elements) {
52+
if (!Types.TryGetValue (e.ActualElementName, out var t)) {
53+
missing.Add ($"- Type: <{e.ActualElementName}>");
54+
continue;
55+
}
5556

56-
foreach (var e in unconsumed)
57-
sb.AppendLine ($"- {e.Path}");
57+
if (t.Ignore)
58+
continue;
5859

59-
throw new InvalidOperationException (sb.ToString ());
60-
}
60+
foreach (var a in e.Attributes) {
61+
var name = $"{e.ActualElementName}.{a.Name}";
6162

62-
public void EnsureMetadataTypesConsumed ()
63-
{
64-
var unconsumed = Types.Values.Where (t => !t.Consumed && !t.Ignore).ToList ();
63+
if (!Elements.TryGetValue (name, out _))
64+
missing.Add ($"- Element: {name}");
65+
}
66+
}
6567

66-
if (unconsumed.Count == 0)
68+
if (missing.Count == 0)
6769
return;
6870

6971
var sb = new StringBuilder ();
70-
sb.AppendLine ("The following metadata types were not consumed:");
72+
sb.AppendLine ("The following manifest elements are not specified in the metadata:");
7173

72-
foreach (var t in unconsumed)
73-
sb.AppendLine ($"- {t.Name}");
74+
foreach (var m in missing)
75+
sb.AppendLine (m);
7476

7577
throw new InvalidOperationException (sb.ToString ());
7678
}
7779

78-
public void EnsureAllTypesAccountedFor (IEnumerable<ElementDefinition> elements)
80+
public void EnsureAllMetadataElementsExistInManifest (List<ElementDefinition> elements)
7981
{
8082
var missing = new List<string> ();
8183

82-
foreach (var e in elements) {
83-
if (!Types.ContainsKey (e.ActualElementName))
84-
missing.Add (e.ActualElementName);
84+
foreach (var type in Types) {
85+
var type_def = elements.FirstOrDefault (e => e.ActualElementName == type.Key);
86+
87+
if (type_def is null) {
88+
missing.Add ($"- Type: {type.Key}");
89+
continue;
90+
}
91+
}
92+
93+
foreach (var type in Elements) {
94+
var type_name = type.Key.FirstSubset ('.');
95+
var elem_name = type.Key.LastSubset ('.');
96+
97+
var type_def = elements.FirstOrDefault (e => e.ActualElementName == type_name);
98+
99+
if (type_def is null) {
100+
missing.Add ($"- Element: {type.Key}");
101+
continue;
102+
}
103+
104+
var elem_def = type_def.Attributes.FirstOrDefault (e => e.Name == elem_name);
105+
106+
if (elem_def is null) {
107+
missing.Add ($"- Element: {type.Key}");
108+
continue;
109+
}
85110
}
86111

87112
if (missing.Count == 0)
88113
return;
89114

90115
var sb = new StringBuilder ();
91-
sb.AppendLine ("The following types were not accounted for:");
116+
sb.AppendLine ("The following elements specified in the metadata were not found in the manifest:");
92117

93-
foreach (var m in missing.Order ())
94-
sb.AppendLine ($"- {m}");
118+
foreach (var e in missing)
119+
sb.AppendLine (e);
95120

96121
throw new InvalidOperationException (sb.ToString ());
97122
}
@@ -100,12 +125,12 @@ public void EnsureAllTypesAccountedFor (IEnumerable<ElementDefinition> elements)
100125
class MetadataElement
101126
{
102127
public string Path { get; set; }
103-
public bool? Visible { get; set; }
128+
public bool Visible { get; set; } = true;
104129
public string? Type { get; set; }
105130
public string? Name { get; set; }
106131
public string? Obsolete { get; set; }
107132
public bool ReadOnly { get; set; }
108-
public bool Consumed { get; set; }
133+
public bool ManualMap { get; set; }
109134

110135
public MetadataElement (string path)
111136
{
@@ -125,8 +150,7 @@ public class MetadataType
125150
public bool IsJniNameProvider { get; set; }
126151
public bool HasDefaultConstructor { get; set; }
127152
public bool IsSealed { get; set; }
128-
public bool Consumed { get; set; }
129-
153+
public bool GenerateMapping { get; set; }
130154

131155
public MetadataType (XElement element)
132156
{
@@ -143,6 +167,7 @@ public MetadataType (XElement element)
143167
IsJniNameProvider = element.GetAttributeBoolOrDefault ("jniNameProvider", false);
144168
HasDefaultConstructor = element.GetAttributeBoolOrDefault("defaultConstructor", true);
145169
IsSealed = element.GetAttributeBoolOrDefault ("sealed", true);
146-
ManagedName = element.Attribute ("managedName")?.Value ?? Name.Unhyphenate ().Capitalize () + "Attribute";
170+
ManagedName = element.Attribute ("managedName")?.Value ?? Name.Unhyphenate ().Capitalize () + "Attribute";
171+
GenerateMapping = element.GetAttributeBoolOrDefault ("generateMapping", true);
147172
}
148173
}

build-tools/manifest-attribute-codegen/Program.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ public static int Main (string [] args)
7474
// Read metadata file
7575
var metadata = new MetadataSource (metadata_file);
7676

77+
// Ensure everything in the Android SDK is accounted for.
78+
// This forces us to handle anything new that's been added to the SDK.
79+
metadata.EnsureAllElementsAccountedFor (merged.Elements);
80+
81+
// Ensure there are no unused elements in the metadata file
82+
metadata.EnsureAllMetadataElementsExistInManifest (merged.Elements);
83+
7784
// Generate manifest attributes C# code
7885
foreach (var type in metadata.Types.Values.Where (t => !t.Ignore)) {
7986
using var w = new StreamWriter (Path.Combine (base_dir, type.OutputFile));
@@ -83,13 +90,6 @@ public static int Main (string [] args)
8390
writer.Write (cw);
8491
}
8592

86-
// Ensure everything we found in the Android SDK is accounted for.
87-
// This forces us to handle anything new that's been added to the SDK.
88-
// metadata.EnsureAllTypesExist (merged.Elements);
89-
metadata.EnsureAllTypesAccountedFor (merged.Elements);
90-
metadata.EnsureMetadataTypesConsumed ();
91-
metadata.EnsureMetadataElementsConsumed ();
92-
9393
return 0;
9494
}
9595
}

build-tools/manifest-attribute-codegen/SourceWriters/AttributeDataClass.cs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ namespace Xamarin.Android.Tools.ManifestAttributeCodeGenerator;
55
// This is the common data class used by both Mono.Android and Xamarin.Android.Build.Tasks.
66
class AttributeDataClass : ClassWriter
77
{
8+
AttributeMappingField? mapping_field;
9+
AttributeMappingStaticConstructor? static_constructor;
10+
11+
static AttributeMappingManualInitializer manual_mapping_partial = AttributeMappingManualInitializer.Create ();
12+
813
public string Namespace { get; set; }
914

1015
public AttributeDataClass (string ns)
@@ -14,8 +19,6 @@ public AttributeDataClass (string ns)
1419

1520
public static AttributeDataClass Create (ElementDefinition attr, MetadataSource metadata, MetadataType type)
1621
{
17-
type.Consumed = true;
18-
1922
var c = new AttributeDataClass (type.Namespace) {
2023
Name = type.ManagedName,
2124
IsPublic = true,
@@ -41,12 +44,12 @@ public static AttributeDataClass Create (ElementDefinition attr, MetadataSource
4144
foreach (var a in attr.Attributes.OrderBy (a => a.Name)) {
4245
var attr_metadata = metadata.GetMetadata ($"{attr.ActualElementName}.{a.Name}");
4346

44-
if (attr_metadata.Visible == false)
47+
if (!attr_metadata.Visible)
4548
continue;
4649

4750
var p = new PropertyWriter {
4851
Name = (attr_metadata.Name ?? a.Name).Capitalize (),
49-
PropertyType = new TypeReferenceWriter (attr_metadata.Type ?? GetAttributeType (a)),
52+
PropertyType = new TypeReferenceWriter (attr_metadata.Type ?? a.GetAttributeType ()),
5053
IsPublic = true,
5154
HasGet = true,
5255
HasSet = true,
@@ -61,17 +64,13 @@ public static AttributeDataClass Create (ElementDefinition attr, MetadataSource
6164
c.Properties.Add (p);
6265
}
6366

64-
return c;
65-
}
67+
// Create mapping field used by Xamarin.Android.Build.Tasks
68+
if (type.GenerateMapping) {
69+
c.mapping_field = AttributeMappingField.Create (type);
70+
c.static_constructor = AttributeMappingStaticConstructor.Create (attr, metadata, type);
71+
}
6672

67-
static string GetAttributeType (AttributeDefinition attr)
68-
{
69-
return attr.Format switch {
70-
"boolean" => "bool",
71-
"integer" => "int",
72-
"string" => "string?",
73-
_ => "string?",
74-
};
73+
return c;
7574
}
7675

7776
public override void Write (CodeWriter writer)
@@ -86,4 +85,19 @@ public override void Write (CodeWriter writer)
8685

8786
base.Write (writer);
8887
}
88+
89+
public override void WriteMembers (CodeWriter writer)
90+
{
91+
base.WriteMembers (writer);
92+
93+
if (mapping_field is not null) {
94+
writer.WriteLineNoIndent ("#if XABT_MANIFEST_EXTENSIONS");
95+
mapping_field?.Write (writer);
96+
writer.WriteLine ();
97+
static_constructor?.Write (writer);
98+
writer.WriteLine ();
99+
manual_mapping_partial?.Write (writer);
100+
writer.WriteLineNoIndent ("#endif");
101+
}
102+
}
89103
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Xamarin.SourceWriter;
2+
3+
namespace Xamarin.Android.Tools.ManifestAttributeCodeGenerator;
4+
5+
class AttributeMappingField : FieldWriter
6+
{
7+
public static AttributeMappingField Create (MetadataType type)
8+
{
9+
var field = new AttributeMappingField {
10+
Name = "mapping",
11+
IsStatic = true,
12+
Type = new TypeReferenceWriter ($"Xamarin.Android.Manifest.ManifestDocumentElement<{type.ManagedName}>"),
13+
Value = $"new (\"{type.Name}\")",
14+
};
15+
16+
return field;
17+
}
18+
}

0 commit comments

Comments
 (0)