Skip to content

Commit 5c5dc08

Browse files
authored
[generator] enum map.csv can set @deprecated-since for enum values (#1070)
Context: dotnet/android#7670 While auditing the `Mono.Android.dll`, we've found that some constants were added to the wrong enums. Consider [`xamarin-android/src/Mono.Android/map.csv`][0], lines 739-743: E,28,android/app/usage/UsageStatsManager.STANDBY_BUCKET_ACTIVE,10,Android.App.Usage.StandbyBucket,Active,remove, E,28,android/app/usage/UsageStatsManager.STANDBY_BUCKET_FREQUENT,30,Android.App.Usage.StandbyBucket,Frequent,remove, E,28,android/app/usage/UsageStatsManager.STANDBY_BUCKET_RARE,40,Android.App.Usage.StandbyBucket,Rare,remove, E,30,android/app/usage/UsageStatsManager.STANDBY_BUCKET_RESTRICTED,45,Android.App.Usage.UsageStatsInterval,Restricted,remove, E,28,android/app/usage/UsageStatsManager.STANDBY_BUCKET_WORKING_SET,20,Android.App.Usage.StandbyBucket,WorkingSet,remove, `STANDBY_BUCKET_RESTRICTED` should have been added to `StandbyBucket` instead of `UsageStatsInterval`. To fix this in a backwards-compatible way, we can add it to both enums: E,30,android/app/usage/UsageStatsManager.STANDBY_BUCKET_RESTRICTED,45,Android.App.Usage.StandbyBucket,Restricted,remove, A,30,,45,Android.App.Usage.UsageStatsInterval,Restricted,remove, However, we have no way to mark `UsageStatsInterval.Restricted` as `[Obsolete]`, so people may still unintentionally use it. Add a new field to our csv that allows us to set a constant's `@deprecated-since` field, the `30` at the end: A,30,,45,Android.App.Usage.UsageStatsInterval,Restricted,remove,,30 This allows us to generate: [global::System.Runtime.Versioning.ObsoletedOSPlatform ("android30.0")] Restricted = 45 Additionally, when the cause is that we accidentally added an invalid value to the enum, we should provide a better obsolete message, letting the user know that the value will not function. This is done by setting `@deprecated-since` to `-1`: A,30,,45,Android.App.Usage.UsageStatsInterval,Restricted,remove,,-1 resulting in: [global::System.Obsolete (@"This value was incorrectly added to the enumeration and is not a valid value")] Restricted = 45 [0]: https://github.com/xamarin/xamarin-android/blob/cc70ce20aff6934532a8cb6a7bddbf3710fd7e1c/src/Mono.Android/map.csv
1 parent f8d77fa commit 5c5dc08

File tree

7 files changed

+103
-10
lines changed

7 files changed

+103
-10
lines changed

Documentation/EnumMappingFile.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ There is now a "v2" version of defining constants. This is a line-level change
6565
and you can mix "v1" and "v2" lines in the same file for backwards compatibility,
6666
but for consistency it's probably better to stick to one style.
6767

68-
A "v2" line contains up to 8 fields:
68+
A "v2" line contains up to 9 fields:
6969

7070
* **Action** - The action to perform. This is what denotes a "v2" line, if the first
7171
character is not one of the following it will be treated as "v1".
@@ -89,10 +89,14 @@ A "v2" line contains up to 8 fields:
8989
* `keep` - Keeps the Java constant
9090
* **Flags** - If this field contains `flags` the enum will be created with the
9191
`[Flags]` attribute. (Any member will `flags` will make the whole enum `[Flags]`.)
92-
92+
* **Deprecated Since** - This is generally only used by `Mono.Android.dll` to denote
93+
the Android level the constant was deprecated in. Specifying "-1" will add an obsolete
94+
message to the effect of: "This value was incorrectly added to the enumeration and is
95+
not a valid value". Leave blank if constant is not deprecated.
96+
9397
Full example:
9498
```
95-
E,10,android/view/Window.PROGRESS_START,0,Android.Views.WindowProgress,Start,remove,flags
99+
E,10,android/view/Window.PROGRESS_START,0,Android.Views.WindowProgress,Start,remove,flags,30
96100
```
97101

98102
[0]: https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/customizing-bindings/java-bindings-metadata#enumfieldsxml-and-enummethodsxml

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class ConstantEntry
1616
public string? EnumMember { get; set; }
1717
public FieldAction FieldAction { get; set; }
1818
public bool IsFlags { get; set; }
19+
public int? DeprecatedSince { get; set; }
1920

2021
public string EnumNamespace {
2122
get {
@@ -133,7 +134,8 @@ static ConstantEntry FromVersion2String (CsvParser parser)
133134
EnumFullType = parser.GetField (4),
134135
EnumMember = parser.GetField (5),
135136
FieldAction = FromFieldActionString (parser.GetField (6)),
136-
IsFlags = parser.GetField (7).ToLowerInvariant () == "flags"
137+
IsFlags = parser.GetField (7).ToLowerInvariant () == "flags",
138+
DeprecatedSince = parser.GetFieldAsNullableInt32 (8)
137139
};
138140

139141
entry.NormalizeJavaSignature ();
@@ -175,7 +177,8 @@ public string ToVersion2String ()
175177
EnumFullType,
176178
EnumMember,
177179
ToConstantFieldActionString (FieldAction),
178-
IsFlags ? "flags" : string.Empty
180+
IsFlags ? "flags" : string.Empty,
181+
DeprecatedSince.HasValue ? DeprecatedSince.Value.ToString () : string.Empty,
179182
};
180183

181184
return string.Join (",", fields);

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

+14
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ public static void SaveEnumMapCsv (List<ConstantEntry> constants, string filenam
4848

4949
public static void SaveEnumMapCsv (List<ConstantEntry> constants, TextWriter writer)
5050
{
51+
var column_names = new [] {
52+
"Action",
53+
"API Level",
54+
"JNI Signature",
55+
"Enum Value",
56+
"C# Enum Type",
57+
"C# Member Name",
58+
"Field Action",
59+
"Is Flags",
60+
"Deprecated Since",
61+
};
62+
63+
writer.WriteLine ("// " + string.Join (",", column_names));
64+
5165
foreach (var c in Sort (constants))
5266
writer.WriteLine (c.ToVersion2String ());
5367
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,15 @@ public int GetFieldAsInt (int index)
2323
{
2424
return int.Parse (GetField (index));
2525
}
26+
27+
public int? GetFieldAsNullableInt32 (int index)
28+
{
29+
var value = GetField (index);
30+
31+
if (int.TryParse (value, out var val))
32+
return val;
33+
34+
return default;
35+
}
2636
}
2737
}

tests/Java.Interop.Tools.Generator-Tests/Enumification/ConstantEntryTests.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public void ParseEnumMapV2 ()
8484
Assert.AreEqual ("Cdsect", entry.EnumMember);
8585
Assert.AreEqual (FieldAction.Keep, entry.FieldAction);
8686
Assert.False (entry.IsFlags);
87+
Assert.IsNull (entry.DeprecatedSince);
8788
}
8889

8990
[Test]
@@ -99,13 +100,14 @@ public void ParseAddEnumMapV2 ()
99100
Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", entry.EnumFullType);
100101
Assert.AreEqual ("Cdsect", entry.EnumMember);
101102
Assert.AreEqual (FieldAction.None, entry.FieldAction);
103+
Assert.IsNull (entry.DeprecatedSince);
102104
Assert.False (entry.IsFlags);
103105
}
104106

105107
[Test]
106108
public void ParseRemoveEnumMapV2 ()
107109
{
108-
var csv = "R,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,,,remove";
110+
var csv = "R,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,,,remove,,33";
109111
var entry = ConstantEntry.FromString (csv);
110112

111113
Assert.AreEqual (ConstantAction.Remove, entry.Action);
@@ -116,6 +118,7 @@ public void ParseRemoveEnumMapV2 ()
116118
Assert.AreEqual (string.Empty, entry.EnumMember);
117119
Assert.AreEqual (FieldAction.Remove, entry.FieldAction);
118120
Assert.False (entry.IsFlags);
121+
Assert.AreEqual (33, entry.DeprecatedSince.Value);
119122
}
120123
}
121124
}

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

+54
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,60 @@ public void ObsoleteFieldButNotEnumAttributeSupport ()
133133
Assert.False (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete(@\"deprecated\")]WithExcluded=1"), writer.ToString ());
134134
}
135135

136+
[Test]
137+
public void ObsoletedOSPlatformAttributeOverrideSupport ()
138+
{
139+
var xml = @"<api>
140+
<package name='java.lang' jni-name='java/lang'>
141+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
142+
</package>
143+
<package name='android.app' jni-name='android/app'>
144+
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='ActivityManager' static='false' visibility='public' jni-signature='Landroid/app/ActivityManager;'>
145+
<field deprecated='not deprecated' final='true' name='RECENT_IGNORE_UNAVAILABLE' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.BIND_CHOOSER_TARGET_SERVICE&quot;' visibility='public' volatile='false' api-since='30' />
146+
</class>
147+
</package>
148+
</api>";
149+
150+
options.UseObsoletedOSPlatformAttributes = true;
151+
152+
var enu = CreateEnum ();
153+
enu.Value.Members.Single (m => m.EnumMember == "WithExcluded").DeprecatedSince = 33;
154+
155+
var gens = ParseApiDefinition (xml);
156+
157+
generator.WriteEnumeration (options, enu, gens.ToArray ());
158+
159+
// The field itself is not deprecated, but [ObsoletedOSPlatform] should be written because we set `DeprecatedSince` on the enum map
160+
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform(\"android33.0\")]WithExcluded=1"), writer.ToString ());
161+
}
162+
163+
[Test]
164+
public void ObsoleteAccidentalAddition ()
165+
{
166+
var xml = @"<api>
167+
<package name='java.lang' jni-name='java/lang'>
168+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
169+
</package>
170+
<package name='android.app' jni-name='android/app'>
171+
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='ActivityManager' static='false' visibility='public' jni-signature='Landroid/app/ActivityManager;'>
172+
<field deprecated='not deprecated' final='true' name='RECENT_IGNORE_UNAVAILABLE' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.BIND_CHOOSER_TARGET_SERVICE&quot;' visibility='public' volatile='false' api-since='30' />
173+
</class>
174+
</package>
175+
</api>";
176+
177+
options.UseObsoletedOSPlatformAttributes = true;
178+
179+
var enu = CreateEnum ();
180+
enu.Value.Members.Single (m => m.EnumMember == "WithExcluded").DeprecatedSince = -1;
181+
182+
var gens = ParseApiDefinition (xml);
183+
184+
generator.WriteEnumeration (options, enu, gens.ToArray ());
185+
186+
// "-1" is a "magic" API level which adds a message indicating the enum value shouldn't even exist
187+
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete(@\"Thisvaluewasincorrectlyaddedtotheenumerationandisnotavalidvalue\")]WithExcluded=1"), writer.ToString ());
188+
}
189+
136190
protected new string GetExpected (string testName)
137191
{
138192
var root = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location);

tools/generator/Java.Interop.Tools.Generator.CodeGeneration/EnumGenerator.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,16 @@ EnumWriter CreateWriter (CodeGenerationOptions opt, KeyValuePair<string, EnumDes
5959

6060
SourceWriterExtensions.AddSupportedOSPlatform (m.Attributes, member.ApiLevel, opt);
6161

62-
// Some of our source fields may have been marked with:
63-
// "This constant will be removed in the future version. Use XXX enum directly instead of this field."
64-
// We don't want this message to propogate to the enum.
65-
if (managedMember != null && managedMember.Value.Field?.DeprecatedComment?.Contains ("enum directly instead of this field") == false)
62+
if (member.DeprecatedSince.HasValue) {
63+
// If we've manually marked it as obsolete in map.csv, that takes precedence
64+
var msg = member.DeprecatedSince.Value == -1 ? "This value was incorrectly added to the enumeration and is not a valid value" : "deprecated";
65+
SourceWriterExtensions.AddObsolete (m.Attributes, msg, opt, deprecatedSince: member.DeprecatedSince);
66+
} else if (managedMember != null && managedMember.Value.Field?.DeprecatedComment?.Contains ("enum directly instead of this field") == false) {
67+
// Some of our source fields may have been marked with:
68+
// "This constant will be removed in the future version. Use XXX enum directly instead of this field."
69+
// We don't want this message to propogate to the enum.
6670
SourceWriterExtensions.AddObsolete (m.Attributes, managedMember.Value.Field.DeprecatedComment, opt, deprecatedSince: managedMember.Value.Field.DeprecatedSince);
71+
}
6772

6873
enoom.Members.Add (m);
6974
}

0 commit comments

Comments
 (0)