Skip to content

Commit 2fae0ee

Browse files
committed
[generator] Support major.minor API levels
Context: dotnet/android#10438 Context: dotnet/android#10438 (comment) Android 16 Quarterly Platform Release 2 (QPR2) (API-CANARY) Beta 1 has been released. What makes API_CANARY unique in the history of Android is that it is a "minor" SDK version: * [`<uses-sdk/>`][3]: > It's not possible to specify that an app either targets or requires a minor SDK version. * [Using new APIs with major and minor releases][4]: > The new [`SDK_INT_FULL`][5] constant can be used for API checks… > > if (SDK_INT_FULL >= VERSION_CODES_FULL.[MAJOR or MINOR RELEASE]) { > // Use APIs introduced in a major or minor release > } > > You can also use the [`Build.getMinorSdkVersion()`][6] method to > get just the minor SDK version: > > minorSdkVersion = Build.getMinorSdkVersion(Build.VERSION_CODES_FULL.BAKLAVA); This is ***not*** "API-37". This is "API-36.1" This impacts *everything*: * `[SupportedOSPlatform]` should include the minor SDK value * `[UnsupportedOSPlatform]` should also include the minor SDK value. * `generator --api-level=API-LEVEL` should include the minor value * … Add a new `Java.Interop.Tools.Generator.ApiLevel` type which holds the Android SDK version, both major and minor values. This is a value type which works like `System.Int32`/`System.Version`. Replace all instances of `int ApiLevel` (and equivalent) with `ApiLevel ApiLevel` (and equivalent). Update `[SupportedOSPlatform]`, `[UnsupportedOSPlatform]`, and `[ObsoletedOSPlatform]` attribute output to include the full SDK value. [3]: https://developer.android.com/guide/topics/manifest/uses-sdk-element [4]: https://developer.android.com/about/versions/16/features#using-new [5]: https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT_FULL [6]: https://developer.android.com/reference/android/os/Build#getMinorSdkVersion(int)
1 parent a5d7370 commit 2fae0ee

File tree

27 files changed

+382
-135
lines changed

27 files changed

+382
-135
lines changed

src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ namespace Java.Interop.Tools.Generator.Enumification
99
public class ConstantEntry
1010
{
1111
public ConstantAction Action { get; set; }
12-
public int ApiLevel { get; set; }
12+
public ApiLevel ApiLevel { get; set; }
1313
public string? JavaSignature { get; set; }
1414
public string? Value { get; set; }
1515
public string? EnumFullType { get; set; }
1616
public string? EnumMember { get; set; }
1717
public FieldAction FieldAction { get; set; }
1818
public bool IsFlags { get; set; }
19-
public int? DeprecatedSince { get; set; }
19+
public ApiLevel? DeprecatedSince { get; set; }
2020

2121
public string EnumNamespace {
2222
get {
@@ -100,7 +100,7 @@ static ConstantEntry FromVersion1String (CsvParser parser, bool transientMode)
100100
{
101101
var entry = new ConstantEntry {
102102
Action = ConstantAction.Enumify,
103-
ApiLevel = parser.GetFieldAsInt (0),
103+
ApiLevel = parser.GetFieldAsApiLevel (0),
104104
EnumFullType = parser.GetField (1),
105105
EnumMember = parser.GetField (2),
106106
JavaSignature = parser.GetField (3),
@@ -128,14 +128,14 @@ static ConstantEntry FromVersion2String (CsvParser parser)
128128
{
129129
var entry = new ConstantEntry {
130130
Action = FromConstantActionString (parser.GetField (0)),
131-
ApiLevel = parser.GetFieldAsInt (1),
131+
ApiLevel = parser.GetFieldAsApiLevel (1),
132132
JavaSignature = parser.GetField (2),
133133
Value = parser.GetField (3),
134134
EnumFullType = parser.GetField (4),
135135
EnumMember = parser.GetField (5),
136136
FieldAction = FromFieldActionString (parser.GetField (6)),
137137
IsFlags = parser.GetField (7).ToLowerInvariant () == "flags",
138-
DeprecatedSince = parser.GetFieldAsNullableInt32 (8)
138+
DeprecatedSince = parser.GetFieldAsNullableApiLevel (8)
139139
};
140140

141141
entry.NormalizeJavaSignature ();

src/Java.Interop.Tools.Generator/Enumification/MethodMapEntry.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Java.Interop.Tools.Generator.Enumification
88
public class MethodMapEntry
99
{
1010
public MethodAction Action { get; set; }
11-
public int ApiLevel { get; set; }
11+
public ApiLevel ApiLevel { get; set; }
1212
public string? JavaPackage { get; set; }
1313
public string? JavaType { get; set; }
1414
public string? JavaName { get; set; }
@@ -66,7 +66,7 @@ static MethodMapEntry FromVersion1String (CsvParser parser)
6666
{
6767
var entry = new MethodMapEntry {
6868
Action = MethodAction.Enumify,
69-
ApiLevel = parser.GetFieldAsInt (0),
69+
ApiLevel = parser.GetFieldAsApiLevel (0),
7070
JavaPackage = parser.GetField (1),
7171
JavaType = parser.GetField (2),
7272
JavaName = parser.GetField (3),
@@ -86,7 +86,7 @@ static MethodMapEntry FromVersion2String (CsvParser parser)
8686
{
8787
var entry = new MethodMapEntry {
8888
Action = FromMethodActionString (parser.GetField (0)),
89-
ApiLevel = parser.GetFieldAsInt (1),
89+
ApiLevel = parser.GetFieldAsApiLevel (1),
9090
JavaPackage = parser.GetField (2),
9191
JavaType = parser.GetField (3),
9292
JavaName = parser.GetField (4),

src/Java.Interop.Tools.Generator/Extensions/XmlExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Xml.Linq;
44
using System.Xml.XPath;
55

6+
using Java.Interop.Tools.Generator;
7+
68
namespace Xamarin.Android.Tools
79
{
810
static class XmlExtensions
@@ -13,11 +15,11 @@ static class XmlExtensions
1315
public static string? XGetAttribute (this XPathNavigator nav, string name, string ns)
1416
=> nav.GetAttribute (name, ns)?.Trim ();
1517

16-
public static int? XGetAttributeAsInt (this XElement element, string name)
18+
public static ApiLevel? XGetAttributeAsApiLevel (this XElement element, string name)
1719
{
1820
var value = element.XGetAttribute (name);
1921

20-
if (int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
22+
if (ApiLevel.TryParse (value, out var result))
2123
return result;
2224

2325
return null;

src/Java.Interop.Tools.Generator/Metadata/FixupXmlDocument.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public FixupXmlDocument (XDocument fixupDocument)
2828
public void Apply (ApiXmlDocument apiDocument, string apiLevelString, int productVersion)
2929
{
3030
// Defaulting to 0 here is fine
31-
int.TryParse (apiLevelString, out var apiLevel);
31+
ApiLevel.TryParse (apiLevelString, out var apiLevel);
3232

3333
var metadataChildren = FixupDocument.XPathSelectElements ("/metadata/*");
3434

@@ -193,22 +193,22 @@ public IList<NamespaceTransform> GetNamespaceTransforms ()
193193
return list;
194194
}
195195

196-
bool ShouldSkip (XElement node, int apiLevel, int productVersion)
196+
bool ShouldSkip (XElement node, ApiLevel apiLevel, int productVersion)
197197
{
198198
if (apiLevel > 0) {
199-
var since = node.XGetAttributeAsInt ("api-since");
200-
var until = node.XGetAttributeAsInt ("api-until");
199+
var since = node.XGetAttributeAsApiLevel ("api-since");
200+
var until = node.XGetAttributeAsApiLevel ("api-until");
201201

202-
if (since is int since_int && since_int > apiLevel)
202+
if (since is ApiLevel since_int && since_int > apiLevel)
203203
return true;
204-
else if (until is int until_int && until_int < apiLevel)
204+
else if (until is ApiLevel until_int && until_int < apiLevel)
205205
return true;
206206
}
207207

208208
if (productVersion > 0) {
209-
var product_version = node.XGetAttributeAsInt ("product-version");
209+
var product_version = node.XGetAttributeAsApiLevel ("product-version");
210210

211-
if (product_version is int version && version > productVersion)
211+
if (product_version is ApiLevel version && version > productVersion)
212212
return true;
213213

214214
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
2+
using System;
3+
using System.Diagnostics.CodeAnalysis;
4+
5+
namespace Java.Interop.Tools.Generator;
6+
7+
public struct ApiLevel : IComparable, IComparable<ApiLevel>, IEquatable<ApiLevel>
8+
{
9+
public int Major { get; private set; }
10+
public int Minor { get; private set; }
11+
12+
public ApiLevel (int major, int minor = 0)
13+
{
14+
Major = major;
15+
Minor = minor;
16+
}
17+
18+
int IComparable.CompareTo (object? value)
19+
{
20+
var other = value as ApiLevel?;
21+
if (other == null) {
22+
return 1;
23+
}
24+
return CompareTo (other.Value);
25+
}
26+
27+
public int CompareTo (ApiLevel value)
28+
{
29+
int r = Major.CompareTo (value.Major);
30+
if (r == 0) {
31+
r = Minor.CompareTo (value.Minor);
32+
}
33+
return r;
34+
}
35+
36+
public override int GetHashCode ()
37+
=> Major ^ Minor;
38+
39+
public override bool Equals (object? value)
40+
{
41+
var other = value as ApiLevel?;
42+
if (other == null) {
43+
return false;
44+
}
45+
return Equals (other.Value);
46+
}
47+
48+
public bool Equals (ApiLevel value)
49+
{
50+
return value.Major == Major && value.Minor == Minor;
51+
}
52+
53+
public override string ToString ()
54+
=> Minor == 0
55+
? Major.ToString ()
56+
: $"{Major}.{Minor}";
57+
58+
// public static implicit operator ApiLevel (int value)
59+
// => new ApiLevel (value);
60+
61+
public static bool operator < (ApiLevel lhs, ApiLevel rhs)
62+
=> lhs.CompareTo (rhs) < 0;
63+
public static bool operator <= (ApiLevel lhs, ApiLevel rhs)
64+
=> lhs.CompareTo (rhs) <= 0;
65+
public static bool operator > (ApiLevel lhs, ApiLevel rhs)
66+
=> lhs.CompareTo (rhs) > 0;
67+
public static bool operator >= (ApiLevel lhs, ApiLevel rhs)
68+
=> lhs.CompareTo (rhs) >= 0;
69+
public static bool operator == (ApiLevel lhs, ApiLevel rhs)
70+
=> lhs.Equals (rhs);
71+
public static bool operator != (ApiLevel lhs, ApiLevel rhs)
72+
=> !lhs.Equals (rhs);
73+
74+
public static bool operator < (ApiLevel lhs, int rhs)
75+
=> lhs.Major.CompareTo (rhs) < 0;
76+
public static bool operator <= (ApiLevel lhs, int rhs)
77+
=> lhs.Major.CompareTo (rhs) <= 0;
78+
public static bool operator > (ApiLevel lhs, int rhs)
79+
=> lhs.Major.CompareTo (rhs) > 0;
80+
public static bool operator >= (ApiLevel lhs, int rhs)
81+
=> lhs.Major.CompareTo (rhs) >= 0;
82+
public static bool operator == (ApiLevel lhs, int rhs)
83+
=> lhs.Major.Equals (rhs);
84+
public static bool operator != (ApiLevel lhs, int rhs)
85+
=> !lhs.Major.Equals (rhs);
86+
87+
public static bool operator < (int lhs, ApiLevel rhs)
88+
=> lhs.CompareTo (rhs.Major) < 0;
89+
public static bool operator <= (int lhs, ApiLevel rhs)
90+
=> lhs.CompareTo (rhs.Major) <= 0;
91+
public static bool operator > (int lhs, ApiLevel rhs)
92+
=> lhs.CompareTo (rhs.Major) > 0;
93+
public static bool operator >= (int lhs, ApiLevel rhs)
94+
=> lhs.CompareTo (rhs.Major) >= 0;
95+
public static bool operator == (int lhs, ApiLevel rhs)
96+
=> lhs.Equals (rhs.Major);
97+
public static bool operator != (int lhs, ApiLevel rhs)
98+
=> !lhs.Equals (rhs.Major);
99+
100+
public static bool TryParse (string? value, out ApiLevel apiLevel)
101+
{
102+
if (value == null) {
103+
apiLevel = default;
104+
return false;
105+
}
106+
if (Version.TryParse (value, out var v)) {
107+
apiLevel = new ApiLevel (v.Major, v.Minor);
108+
return true;
109+
}
110+
if (int.TryParse (value, out var major)) {
111+
apiLevel = new ApiLevel (major);
112+
return true;
113+
}
114+
apiLevel = default;
115+
return false;
116+
}
117+
118+
public static ApiLevel Parse (string? value)
119+
{
120+
ApiLevel v;
121+
if (TryParse (value, out v)) {
122+
return v;
123+
}
124+
throw new NotSupportedException ($"Could not parse `{value}` as an ApiLevel.");
125+
}
126+
}

src/Java.Interop.Tools.Generator/Utilities/CsvParser.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,20 @@ public int GetFieldAsInt (int index)
3333

3434
return default;
3535
}
36+
37+
public ApiLevel GetFieldAsApiLevel (int index)
38+
{
39+
return ApiLevel.Parse (GetField (index));
40+
}
41+
42+
public ApiLevel? GetFieldAsNullableApiLevel (int index)
43+
{
44+
var value = GetField (index);
45+
46+
if (ApiLevel.TryParse (value, out var val))
47+
return val;
48+
49+
return default;
50+
}
3651
}
3752
}

src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,26 @@ public static class NamingConverter
99
/// <summary>
1010
/// Converts a 'merge.SourceFile' attribute to an API level. (ex. "..\..\bin\BuildDebug\api\api-28.xml.in")
1111
/// </summary>
12-
public static int ParseApiLevel (string? value)
12+
public static ApiLevel ParseApiLevel (string? value)
1313
{
1414
if (!value.HasValue ())
15-
return 0;
15+
return default;
1616

1717
var hyphen = value.IndexOf ('-');
1818
var period = value.IndexOf ('.', hyphen);
1919

2020
var result = value.Substring (hyphen + 1, period - hyphen - 1);
2121

2222
return result switch {
23-
"R" => 30,
24-
"S" => 31,
25-
_ => int.Parse (result)
23+
"R" => new ApiLevel (30),
24+
"S" => new ApiLevel (31),
25+
_ => ApiLevel.Parse (result)
2626
};
2727
}
2828

2929
// The 'merge.SourceFile' attribute may be on the element, or only on its parent. For example,
3030
// a new 'class' added will only put the attribute on the '<class>' element and not its children <method>s.
31-
public static int ParseApiLevel (XElement element)
31+
public static ApiLevel ParseApiLevel (XElement element)
3232
{
3333
var loop = element;
3434

@@ -39,7 +39,7 @@ public static int ParseApiLevel (XElement element)
3939
loop = loop.Parent;
4040
}
4141

42-
return 0;
42+
return default;
4343
}
4444

4545
public static string ConvertNamespaceToCSharp (string v)

src/utils/XmlExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Xml.Linq;
66
using System.Xml.XPath;
77

8+
using Java.Interop.Tools.Generator;
9+
810
namespace Xamarin.Android.Tools {
911

1012
static class XmlExtensions {
@@ -33,5 +35,18 @@ public static string XGetAttribute (this XPathNavigator nav, string name, string
3335

3436
return null;
3537
}
38+
39+
public static ApiLevel? XGetAttributeAsApiLevelOrNull (this XElement element, string name)
40+
{
41+
var attr = element.Attribute (name);
42+
43+
if (attr?.Value is null)
44+
return null;
45+
46+
if (ApiLevel.TryParse (attr.Value, out var val))
47+
return val;
48+
49+
return null;
50+
}
3651
}
3752
}

0 commit comments

Comments
 (0)