Skip to content

Commit d6024f1

Browse files
jpobstjonpryor
authored andcommitted
[generator] Use //*/@api-since for RegisterAttribute.ApiSince (#644)
Context: 005e273 Currently, if `generator --apiversions=FILE` is used, then the `RegisterAttribute.ApiSince` field will be set to contain the API version which introduced a method, e.g. when building xamarin-android's `src/Mono.Android`: $ generator.exe --apiversions=$HOME/android-toolchain/sdk/platform-tools/api/api-versions.xml … and `$HOME/android-toolchain/sdk/platform-tools/api/api-versions.xml` contains: <class name="android/view/inspector/IntFlagMapping" since="29"> <extends name="java/lang/Object"/> then `generator` will emit: [Register ("android/view/inspector/IntFlagMapping", DoNotGenerateAcw=true, ApiSince=29)] partial class IntFlagMapping {} Unfortunately, we have found that Google's support of this file isn't guaranteed. Sometimes it is missing, other times it changes in unexpected ways, causing `ApiCompat` breakage within xamarin-android. In short, we have lost faith in `api-versions.xml`. Instead of using `api-versions.xml`, allow `RegisterAttribute.ApiSince` to be populated by a new `//@api-since` attribute, which is now honored on `package`, `class`/`interface`, `method`, and `field` elements: <field name='VERSION_CODE' api-since='7' /> This allows it to be set via `metadata` or any external tooling that modifies `api.xml`. Note that members will inherit versions from parent types and parent package definitions, but can also override them as necessary. For example a package added in API-7 containing a class added in API-9: <package name='com.example.test' jni-name='com/example/test' api-since='7'> <class name='MyClassFrom7' /> <class name='MyClassFrom9' api-since='9' /> </package> As previously, this feature is probably only useful for `Mono.Android.dll` purposes, but it is now more accessible.
1 parent 3968236 commit d6024f1

File tree

3 files changed

+153
-8
lines changed

3 files changed

+153
-8
lines changed

tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,35 @@ public void CreateClass_EnsureValidName ()
1919
Assert.AreEqual ("_3", klass.Name);
2020
}
2121

22+
[Test]
23+
public void CreateClass_CorrectApiSince ()
24+
{
25+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><class name='myclass' api-since='7' /></package>");
26+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);
27+
28+
Assert.AreEqual (7, klass.ApiAvailableSince);
29+
}
30+
31+
[Test]
32+
public void CreateClass_CorrectApiSinceFromPackage ()
33+
{
34+
// Make sure we inherit it from <package>.
35+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><class name='myclass' /></package>");
36+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);
37+
38+
Assert.AreEqual (7, klass.ApiAvailableSince);
39+
}
40+
41+
[Test]
42+
public void CreateClass_CorrectApiSinceOverridePackage ()
43+
{
44+
// Make sure we inherit it from <package>.
45+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><class name='myclass' api-since='9' /></package>");
46+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);
47+
48+
Assert.AreEqual (9, klass.ApiAvailableSince);
49+
}
50+
2251
[Test]
2352
public void CreateCtor_EnsureValidName ()
2453
{
@@ -28,42 +57,84 @@ public void CreateCtor_EnsureValidName ()
2857
Assert.AreEqual ("_3", klass.Ctors[0].Name);
2958
}
3059

60+
[Test]
61+
public void CreateCtor_CorrectApiSince ()
62+
{
63+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><class name='test'><constructor name='ctor' api-since='7' /></class></package>");
64+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);
65+
66+
Assert.AreEqual (7, klass.Ctors [0].ApiAvailableSince);
67+
}
68+
69+
[Test]
70+
public void CreateCtor_CorrectApiSinceFromClass ()
71+
{
72+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><class name='test' api-since='7'><constructor name='ctor' /></class></package>");
73+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);
74+
75+
Assert.AreEqual (7, klass.Ctors [0].ApiAvailableSince);
76+
}
77+
3178
[Test]
3279
public void CreateField_StudlyCaseName ()
3380
{
81+
var klass = new TestClass ("object", "MyNamespace.MyType");
3482
var xml = XDocument.Parse ("<field name=\"_DES_EDE_CBC\" />");
35-
var field = XmlApiImporter.CreateField (xml.Root);
83+
var field = XmlApiImporter.CreateField (klass, xml.Root);
3684

3785
Assert.AreEqual ("DesEdeCbc", field.Name);
3886
}
3987

4088
[Test]
4189
public void CreateField_EnsureValidName ()
4290
{
91+
var klass = new TestClass ("object", "MyNamespace.MyType");
4392
var xml = XDocument.Parse ("<field name=\"_3DES_EDE_CBC\" />");
44-
var field = XmlApiImporter.CreateField (xml.Root);
93+
var field = XmlApiImporter.CreateField (klass, xml.Root);
4594

4695
Assert.AreEqual ("_3desEdeCbc", field.Name);
4796
}
4897

4998
[Test]
5099
public void CreateField_HandleDollarSign ()
51100
{
101+
var klass = new TestClass ("object", "MyNamespace.MyType");
52102
var xml = XDocument.Parse ("<field name=\"A$3\" />");
53-
var field = XmlApiImporter.CreateField (xml.Root);
103+
var field = XmlApiImporter.CreateField (klass, xml.Root);
54104

55105
Assert.AreEqual ("A_3", field.Name);
56106
}
57107

58108
[Test]
59109
public void CreateField_HandleDollarSignNumber ()
60110
{
111+
var klass = new TestClass ("object", "MyNamespace.MyType");
61112
var xml = XDocument.Parse ("<field name=\"$3\" />");
62-
var field = XmlApiImporter.CreateField (xml.Root);
113+
var field = XmlApiImporter.CreateField (klass, xml.Root);
63114

64115
Assert.AreEqual ("_3", field.Name);
65116
}
66117

118+
[Test]
119+
public void CreateField_CorrectApiVersion ()
120+
{
121+
var klass = new TestClass ("object", "MyNamespace.MyType");
122+
var xml = XDocument.Parse ("<field name='$3' api-since='7' />");
123+
var field = XmlApiImporter.CreateField (klass, xml.Root);
124+
125+
Assert.AreEqual (7, field.ApiAvailableSince);
126+
}
127+
128+
[Test]
129+
public void CreateField_CorrectApiVersionFromClass ()
130+
{
131+
var klass = new TestClass ("object", "MyNamespace.MyType") { ApiAvailableSince = 7 };
132+
var xml = XDocument.Parse ("<field name='$3' />");
133+
var field = XmlApiImporter.CreateField (klass, xml.Root);
134+
135+
Assert.AreEqual (7, field.ApiAvailableSince);
136+
}
137+
67138
[Test]
68139
public void CreateInterface_EnsureValidName ()
69140
{
@@ -73,6 +144,35 @@ public void CreateInterface_EnsureValidName ()
73144
Assert.AreEqual ("I_3", iface.Name);
74145
}
75146

147+
[Test]
148+
public void CreateInterface_CorrectApiSince ()
149+
{
150+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><interface name='myclass' api-since='7' /></package>");
151+
var iface = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("interface"), opt);
152+
153+
Assert.AreEqual (7, iface.ApiAvailableSince);
154+
}
155+
156+
[Test]
157+
public void CreateInterface_CorrectApiSinceFromPackage ()
158+
{
159+
// Make sure we inherit it from <package>.
160+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><interface name='myclass' /></package>");
161+
var iface = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("interface"), opt);
162+
163+
Assert.AreEqual (7, iface.ApiAvailableSince);
164+
}
165+
166+
[Test]
167+
public void CreateInterface_CorrectApiSinceOverridePackage ()
168+
{
169+
// Make sure we inherit it from <package>.
170+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><interface name='myclass' api-since='9' /></package>");
171+
var iface = XmlApiImporter.CreateInterface (xml.Root, xml.Root.Element ("interface"), opt);
172+
173+
Assert.AreEqual (9, iface.ApiAvailableSince);
174+
}
175+
76176
[Test]
77177
public void CreateMethod_EnsureValidName ()
78178
{
@@ -91,6 +191,24 @@ public void CreateMethod_EnsureValidNameHyphen ()
91191
Assert.AreEqual ("_3", klass.Methods [0].Name);
92192
}
93193

194+
[Test]
195+
public void CreateMethod_CorrectApiSince ()
196+
{
197+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><class name='test'><method name='-3' api-since='7' /></class></package>");
198+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);
199+
200+
Assert.AreEqual (7, klass.Methods [0].ApiAvailableSince);
201+
}
202+
203+
[Test]
204+
public void CreateMethod_CorrectApiSinceFromClass ()
205+
{
206+
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><class name='test' api-since='7'><method name='-3' /></class></package>");
207+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);
208+
209+
Assert.AreEqual (7, klass.Methods [0].ApiAvailableSince);
210+
}
211+
94212
[Test]
95213
public void CreateParameter_EnsureValidName ()
96214
{

tests/generator-Tests/Unit-Tests/XmlTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void Field ()
171171
{
172172
var element = package.Element ("class");
173173
var @class = XmlApiImporter.CreateClass (package, element, options);
174-
var field = XmlApiImporter.CreateField (element.Element ("field"));
174+
var field = XmlApiImporter.CreateField (@class, element.Element ("field"));
175175
Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "field.Validate failed!");
176176

177177
Assert.AreEqual ("Value", field.Name);

tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
2525
!options.SupportNestedInterfaceTypes
2626
};
2727

28+
FillApiSince (klass, pkg, elem);
29+
2830
foreach (var child in elem.Elements ()) {
2931
switch (child.Name.LocalName) {
3032
case "implements":
@@ -39,7 +41,7 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
3941
klass.Ctors.Add (CreateCtor (klass, child));
4042
break;
4143
case "field":
42-
klass.AddField (CreateField (child));
44+
klass.AddField (CreateField (klass, child));
4345
break;
4446
case "typeParameters":
4547
break; // handled at GenBaseSupport
@@ -55,6 +57,7 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
5557
public static Ctor CreateCtor (GenBase declaringType, XElement elem)
5658
{
5759
var ctor = new Ctor (declaringType) {
60+
ApiAvailableSince = declaringType.ApiAvailableSince,
5861
CustomAttributes = elem.XGetAttribute ("customAttributes"),
5962
Deprecated = elem.Deprecated (),
6063
GenericArguments = elem.GenericArguments (),
@@ -90,12 +93,15 @@ public static Ctor CreateCtor (GenBase declaringType, XElement elem)
9093

9194
ctor.Name = EnsureValidIdentifer (ctor.Name);
9295

96+
FillApiSince (ctor, elem);
97+
9398
return ctor;
9499
}
95100

96-
public static Field CreateField (XElement elem)
101+
public static Field CreateField (GenBase declaringType, XElement elem)
97102
{
98103
var field = new Field {
104+
ApiAvailableSince = declaringType.ApiAvailableSince,
99105
DeprecatedComment = elem.XGetAttribute ("deprecated"),
100106
IsAcw = true,
101107
IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated",
@@ -124,6 +130,8 @@ public static Field CreateField (XElement elem)
124130
field.Name = EnsureValidIdentifer (field.Name);
125131
}
126132

133+
FillApiSince (field, elem);
134+
127135
return field;
128136
}
129137

@@ -205,6 +213,8 @@ public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGen
205213
!options.SupportNestedInterfaceTypes
206214
};
207215

216+
FillApiSince (iface, pkg, elem);
217+
208218
foreach (var child in elem.Elements ()) {
209219
switch (child.Name.LocalName) {
210220
case "implements":
@@ -216,7 +226,7 @@ public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGen
216226
iface.AddMethod (CreateMethod (iface, child));
217227
break;
218228
case "field":
219-
iface.AddField (CreateField (child));
229+
iface.AddField (CreateField (iface, child));
220230
break;
221231
case "typeParameters":
222232
break; // handled at GenBaseSupport
@@ -232,6 +242,7 @@ public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGen
232242
public static Method CreateMethod (GenBase declaringType, XElement elem)
233243
{
234244
var method = new Method (declaringType) {
245+
ApiAvailableSince = declaringType.ApiAvailableSince,
235246
ArgsType = elem.Attribute ("argsType")?.Value,
236247
CustomAttributes = elem.XGetAttribute ("customAttributes"),
237248
Deprecated = elem.Deprecated (),
@@ -279,6 +290,8 @@ public static Method CreateMethod (GenBase declaringType, XElement elem)
279290

280291
method.FillReturnType ();
281292

293+
FillApiSince (method, elem);
294+
282295
return method;
283296
}
284297

@@ -350,6 +363,20 @@ static XElement GetPreviousClass (XNode n, string nameValue)
350363
return e;
351364
}
352365

366+
// The array here allows members to inherit defaults from their parent, but
367+
// override them if they were added later.
368+
// For example:
369+
// - <package api-since="21">
370+
// - <class api-since="24">
371+
// - <method api-since="28">
372+
// Elements need to be passed in the above order. (package, class, member)
373+
static void FillApiSince (ApiVersionsSupport.IApiAvailability model, params XElement[] elems)
374+
{
375+
foreach (var elem in elems)
376+
if (int.TryParse (elem.XGetAttribute ("api-since"), out var result))
377+
model.ApiAvailableSince = result;
378+
}
379+
353380
static bool IsObfuscatedName (int threshold, string name)
354381
{
355382
if (name.StartsWith ("R.", StringComparison.Ordinal))

0 commit comments

Comments
 (0)