Skip to content

Commit

Permalink
[generator] Use //*/@api-since for RegisterAttribute.ApiSince (#644)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jpobst authored May 11, 2020
1 parent 010161d commit 186174c
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 8 deletions.
126 changes: 122 additions & 4 deletions tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,35 @@ public void CreateClass_EnsureValidName ()
Assert.AreEqual ("_3", klass.Name);
}

[Test]
public void CreateClass_CorrectApiSince ()
{
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><class name='myclass' api-since='7' /></package>");
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);

Assert.AreEqual (7, klass.ApiAvailableSince);
}

[Test]
public void CreateClass_CorrectApiSinceFromPackage ()
{
// Make sure we inherit it from <package>.
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><class name='myclass' /></package>");
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);

Assert.AreEqual (7, klass.ApiAvailableSince);
}

[Test]
public void CreateClass_CorrectApiSinceOverridePackage ()
{
// Make sure we inherit it from <package>.
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><class name='myclass' api-since='9' /></package>");
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);

Assert.AreEqual (9, klass.ApiAvailableSince);
}

[Test]
public void CreateCtor_EnsureValidName ()
{
Expand All @@ -28,42 +57,84 @@ public void CreateCtor_EnsureValidName ()
Assert.AreEqual ("_3", klass.Ctors[0].Name);
}

[Test]
public void CreateCtor_CorrectApiSince ()
{
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>");
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);

Assert.AreEqual (7, klass.Ctors [0].ApiAvailableSince);
}

[Test]
public void CreateCtor_CorrectApiSinceFromClass ()
{
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>");
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);

Assert.AreEqual (7, klass.Ctors [0].ApiAvailableSince);
}

[Test]
public void CreateField_StudlyCaseName ()
{
var klass = new TestClass ("object", "MyNamespace.MyType");
var xml = XDocument.Parse ("<field name=\"_DES_EDE_CBC\" />");
var field = XmlApiImporter.CreateField (xml.Root);
var field = XmlApiImporter.CreateField (klass, xml.Root);

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

[Test]
public void CreateField_EnsureValidName ()
{
var klass = new TestClass ("object", "MyNamespace.MyType");
var xml = XDocument.Parse ("<field name=\"_3DES_EDE_CBC\" />");
var field = XmlApiImporter.CreateField (xml.Root);
var field = XmlApiImporter.CreateField (klass, xml.Root);

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

[Test]
public void CreateField_HandleDollarSign ()
{
var klass = new TestClass ("object", "MyNamespace.MyType");
var xml = XDocument.Parse ("<field name=\"A$3\" />");
var field = XmlApiImporter.CreateField (xml.Root);
var field = XmlApiImporter.CreateField (klass, xml.Root);

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

[Test]
public void CreateField_HandleDollarSignNumber ()
{
var klass = new TestClass ("object", "MyNamespace.MyType");
var xml = XDocument.Parse ("<field name=\"$3\" />");
var field = XmlApiImporter.CreateField (xml.Root);
var field = XmlApiImporter.CreateField (klass, xml.Root);

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

[Test]
public void CreateField_CorrectApiVersion ()
{
var klass = new TestClass ("object", "MyNamespace.MyType");
var xml = XDocument.Parse ("<field name='$3' api-since='7' />");
var field = XmlApiImporter.CreateField (klass, xml.Root);

Assert.AreEqual (7, field.ApiAvailableSince);
}

[Test]
public void CreateField_CorrectApiVersionFromClass ()
{
var klass = new TestClass ("object", "MyNamespace.MyType") { ApiAvailableSince = 7 };
var xml = XDocument.Parse ("<field name='$3' />");
var field = XmlApiImporter.CreateField (klass, xml.Root);

Assert.AreEqual (7, field.ApiAvailableSince);
}

[Test]
public void CreateInterface_EnsureValidName ()
{
Expand All @@ -73,6 +144,35 @@ public void CreateInterface_EnsureValidName ()
Assert.AreEqual ("I_3", iface.Name);
}

[Test]
public void CreateInterface_CorrectApiSince ()
{
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test'><interface name='myclass' api-since='7' /></package>");
var iface = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("interface"), opt);

Assert.AreEqual (7, iface.ApiAvailableSince);
}

[Test]
public void CreateInterface_CorrectApiSinceFromPackage ()
{
// Make sure we inherit it from <package>.
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><interface name='myclass' /></package>");
var iface = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("interface"), opt);

Assert.AreEqual (7, iface.ApiAvailableSince);
}

[Test]
public void CreateInterface_CorrectApiSinceOverridePackage ()
{
// Make sure we inherit it from <package>.
var xml = XDocument.Parse ("<package name='com.example.test' jni-name='com/example/test' api-since='7'><interface name='myclass' api-since='9' /></package>");
var iface = XmlApiImporter.CreateInterface (xml.Root, xml.Root.Element ("interface"), opt);

Assert.AreEqual (9, iface.ApiAvailableSince);
}

[Test]
public void CreateMethod_EnsureValidName ()
{
Expand All @@ -91,6 +191,24 @@ public void CreateMethod_EnsureValidNameHyphen ()
Assert.AreEqual ("_3", klass.Methods [0].Name);
}

[Test]
public void CreateMethod_CorrectApiSince ()
{
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>");
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);

Assert.AreEqual (7, klass.Methods [0].ApiAvailableSince);
}

[Test]
public void CreateMethod_CorrectApiSinceFromClass ()
{
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>");
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt);

Assert.AreEqual (7, klass.Methods [0].ApiAvailableSince);
}

[Test]
public void CreateParameter_EnsureValidName ()
{
Expand Down
2 changes: 1 addition & 1 deletion tests/generator-Tests/Unit-Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public void Field ()
{
var element = package.Element ("class");
var @class = XmlApiImporter.CreateClass (package, element, options);
var field = XmlApiImporter.CreateField (element.Element ("field"));
var field = XmlApiImporter.CreateField (@class, element.Element ("field"));
Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "field.Validate failed!");

Assert.AreEqual ("Value", field.Name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
!options.SupportNestedInterfaceTypes
};

FillApiSince (klass, pkg, elem);

foreach (var child in elem.Elements ()) {
switch (child.Name.LocalName) {
case "implements":
Expand All @@ -39,7 +41,7 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
klass.Ctors.Add (CreateCtor (klass, child));
break;
case "field":
klass.AddField (CreateField (child));
klass.AddField (CreateField (klass, child));
break;
case "typeParameters":
break; // handled at GenBaseSupport
Expand All @@ -55,6 +57,7 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
public static Ctor CreateCtor (GenBase declaringType, XElement elem)
{
var ctor = new Ctor (declaringType) {
ApiAvailableSince = declaringType.ApiAvailableSince,
CustomAttributes = elem.XGetAttribute ("customAttributes"),
Deprecated = elem.Deprecated (),
GenericArguments = elem.GenericArguments (),
Expand Down Expand Up @@ -90,12 +93,15 @@ public static Ctor CreateCtor (GenBase declaringType, XElement elem)

ctor.Name = EnsureValidIdentifer (ctor.Name);

FillApiSince (ctor, elem);

return ctor;
}

public static Field CreateField (XElement elem)
public static Field CreateField (GenBase declaringType, XElement elem)
{
var field = new Field {
ApiAvailableSince = declaringType.ApiAvailableSince,
DeprecatedComment = elem.XGetAttribute ("deprecated"),
IsAcw = true,
IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated",
Expand Down Expand Up @@ -124,6 +130,8 @@ public static Field CreateField (XElement elem)
field.Name = EnsureValidIdentifer (field.Name);
}

FillApiSince (field, elem);

return field;
}

Expand Down Expand Up @@ -205,6 +213,8 @@ public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGen
!options.SupportNestedInterfaceTypes
};

FillApiSince (iface, pkg, elem);

foreach (var child in elem.Elements ()) {
switch (child.Name.LocalName) {
case "implements":
Expand All @@ -216,7 +226,7 @@ public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGen
iface.AddMethod (CreateMethod (iface, child));
break;
case "field":
iface.AddField (CreateField (child));
iface.AddField (CreateField (iface, child));
break;
case "typeParameters":
break; // handled at GenBaseSupport
Expand All @@ -232,6 +242,7 @@ public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGen
public static Method CreateMethod (GenBase declaringType, XElement elem)
{
var method = new Method (declaringType) {
ApiAvailableSince = declaringType.ApiAvailableSince,
ArgsType = elem.Attribute ("argsType")?.Value,
CustomAttributes = elem.XGetAttribute ("customAttributes"),
Deprecated = elem.Deprecated (),
Expand Down Expand Up @@ -279,6 +290,8 @@ public static Method CreateMethod (GenBase declaringType, XElement elem)

method.FillReturnType ();

FillApiSince (method, elem);

return method;
}

Expand Down Expand Up @@ -350,6 +363,20 @@ static XElement GetPreviousClass (XNode n, string nameValue)
return e;
}

// The array here allows members to inherit defaults from their parent, but
// override them if they were added later.
// For example:
// - <package api-since="21">
// - <class api-since="24">
// - <method api-since="28">
// Elements need to be passed in the above order. (package, class, member)
static void FillApiSince (ApiVersionsSupport.IApiAvailability model, params XElement[] elems)
{
foreach (var elem in elems)
if (int.TryParse (elem.XGetAttribute ("api-since"), out var result))
model.ApiAvailableSince = result;
}

static bool IsObfuscatedName (int threshold, string name)
{
if (name.StartsWith ("R.", StringComparison.Ordinal))
Expand Down

0 comments on commit 186174c

Please sign in to comment.