Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 5e6209e

Browse files
authored
[generator] Obsolete&SupportedOSPlatform attributes on enum members (#1066)
Fixes: #1037 Adds support for emitting `[Obsolete]`, `[ObsoletedOSPlatform]`, and `[SupportedOSPlatform]` custom attributes on enum members. ~~ [SupportedOSPlatform] ~~ The data for `[SupportedOSPlatform]` comes from the values provided in the enum-mapping file (b00e644), e.g. [`xamarin-android/src/Mono.Android/map.csv`][0]: // src/Mono.Android/map.csv E,29,android/media/MediaRecorder$AudioEncoder.OPUS,7,Android.Media.AudioEncoder,Opus,keep, The `29` is the API in which we added the enum, which becomes `[SupportedOSPlatform("android-29.0")]`. ~~ [Obsolete] and [ObsoletedOSPlatform] ~~ The data for the "obsolete" attributes comes from the `deprecated` and `deprecated-since` attributes on the original field in the `api.xml`, if it can be found. For example, given: <class name="AccessibilityServiceInfo" jni-signature="Landroid/accessibilityservice/AccessibilityServiceInfo;" …> <field deprecated="deprecated" final="true" name="CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY" jni-signature="I" static="true" transient="false" type="int" type-generic-aware="int" value="4" visibility="public" volatile="false" deprecated-since="26" /> </class> in combination with these `map.csv` (b00e644) entries: A,0,,0,Android.AccessibilityServices.AccessibilityServiceCapabilities,None,remove, E,18,android/accessibilityservice/AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,4,Android.AccessibilityServices.AccessibilityServiceCapabilities,CanRequestEnhancedWebAccessibility,remove, results in: /* partial */ enum AccessibilityServiceCapabilities { // When using classic obsolete attributes: [global::System.Obsolete("deprecated")] // When using new obsolete attributes: [global::System.Runtime.Versioning.ObsoletedOSPlatform("android31.0")] CanRequestEnhancedWebAccessibility = 4, } One wrinkle is we may have obsoleted the field because we want the user to use the enum instead: <field deprecated='This constant will be removed in the future version. Use Android.App.RecentTaskFlags enum directly instead of this field.' name='RECENT_IGNORE_UNAVAILABLE' … /> We need to detect this message and not obsolete the enum in this case. [0]: https://github.com/xamarin/xamarin-android/blob/17213ea184e23a9020451b265fec459558278489/src/Mono.Android/map.csv
1 parent 15c8879 commit 5e6209e

3 files changed

Lines changed: 103 additions & 21 deletions

File tree

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

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
usingSystem;
22
usingSystem.Collections.Generic;
33
usingSystem.IO;
4+
usingSystem.Linq;
45
usingSystem.Reflection;
56
usingSystem.Text;
67
usingJava.Interop.Tools.Generator.Enumification;
78
usingMonoDroid.Generation;
89
usingNUnit.Framework;
910
usingNUnit.Framework.Internal;
11+
usingXamarin.Android.Binder;
1012

1113
namespacegeneratortests
1214
{
1315
[TestFixture]
14-
classEnumGeneratorTests
16+
classEnumGeneratorTests:CodeGeneratorTestBase
1517
{
16-
protectedEnumGeneratorgenerator;
17-
protectedStringBuilderbuilder;
18-
protectedStringWriterwriter;
18+
protectednewEnumGeneratorgenerator;
19+
20+
protectedoverrideCodeGenerationTargetTarget=>CodeGenerationTarget.XAJavaInterop1;
1921

2022
[SetUp]
21-
publicvoidSetUp()
23+
publicnewvoidSetUp()
2224
{
2325
builder=newStringBuilder();
2426
writer=newStringWriter(builder);
@@ -60,7 +62,78 @@ public void WriteEnumWithGens ()
6062
Assert.AreEqual(GetExpected(nameof(WriteEnumWithGens)),writer.ToString().NormalizeLineEndings());
6163
}
6264

63-
protectedstringGetExpected(stringtestName)
65+
[Test]
66+
publicvoidObsoletedOSPlatformAttributeSupport()
67+
{
68+
varxml=@"<api>
69+
<package name='java.lang' jni-name='java/lang'>
70+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
71+
</package>
72+
<package name='android.app' jni-name='android/app'>
73+
<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;'>
74+
<field deprecated='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' deprecated-since='31' api-since='30' />
75+
</class>
76+
</package>
77+
</api>";
78+
79+
options.UseObsoletedOSPlatformAttributes=true;
80+
81+
varenu=CreateEnum();
82+
vargens=ParseApiDefinition(xml);
83+
84+
generator.WriteEnumeration(options,enu,gens.ToArray());
85+
86+
// Ensure [ObsoletedOSPlatform] and [SupportedOSPlatform] are written
87+
Assert.True(writer.ToString().NormalizeLineEndings().Contains("[global::System.Runtime.Versioning.SupportedOSPlatformAttribute(\"android30.0\")][global::System.Runtime.Versioning.ObsoletedOSPlatform(\"android31.0\")]WithExcluded=1"),writer.ToString());
88+
}
89+
90+
[Test]
91+
publicvoidObsoleteAttributeSupport()
92+
{
93+
varxml=@"<api>
94+
<package name='java.lang' jni-name='java/lang'>
95+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
96+
</package>
97+
<package name='android.app' jni-name='android/app'>
98+
<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;'>
99+
<field deprecated='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' deprecated-since='31' api-since='30' />
100+
</class>
101+
</package>
102+
</api>";
103+
104+
varenu=CreateEnum();
105+
vargens=ParseApiDefinition(xml);
106+
107+
generator.WriteEnumeration(options,enu,gens.ToArray());
108+
109+
// Ensure [Obsolete] is written
110+
Assert.True(writer.ToString().NormalizeLineEndings().Contains("[global::System.Obsolete(@\"deprecated\")]WithExcluded=1"),writer.ToString());
111+
}
112+
113+
[Test]
114+
publicvoidObsoleteFieldButNotEnumAttributeSupport()
115+
{
116+
varxml=@"<api>
117+
<package name='java.lang' jni-name='java/lang'>
118+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
119+
</package>
120+
<package name='android.app' jni-name='android/app'>
121+
<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;'>
122+
<field deprecated='This constant will be removed in the future version. Use Android.App.RecentTaskFlags enum directly instead of this field.' 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' deprecated-since='31' api-since='30' />
123+
</class>
124+
</package>
125+
</api>";
126+
127+
varenu=CreateEnum();
128+
vargens=ParseApiDefinition(xml);
129+
130+
generator.WriteEnumeration(options,enu,gens.ToArray());
131+
132+
// [Obsolete] should not be written because the value isn't deprecated, just the _field_ is deprecated because we want people to use the enum instead
133+
Assert.False(writer.ToString().NormalizeLineEndings().Contains("[global::System.Obsolete(@\"deprecated\")]WithExcluded=1"),writer.ToString());
134+
}
135+
136+
protectednewstringGetExpected(stringtestName)
64137
{
65138
varroot=Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
66139

@@ -71,7 +144,7 @@ protected string GetExpected (string testName)
71144
{
72145
varenu=newEnumMappings.EnumDescription{
73146
Members=newList<ConstantEntry>{
74-
newConstantEntry{EnumMember="WithExcluded",Value="1",JavaSignature="android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE"},
147+
newConstantEntry{EnumMember="WithExcluded",Value="1",JavaSignature="android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE",ApiLevel=30},
75148
newConstantEntry{EnumMember="IgnoreUnavailable",Value="2",JavaSignature="android/app/ActivityManager.RECENT_WITH_EXCLUDED"}
76149
},
77150
BitField=false,

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,36 +50,43 @@ EnumWriter CreateWriter (CodeGenerationOptions opt, KeyValuePair<string, EnumDes
5050
Value=member.Value.Trim(),
5151
};
5252

53+
// Try to find the original field in our model
5354
varmanagedMember=FindManagedMember(enu.Value,member,gens);
55+
varmanagedMemberName=managedMember!=null?$"{managedMember.Value.Cls.FullName}.{managedMember.Value.Field.Name}":null;
5456

5557
if(opt.CodeGenerationTarget!=CodeGenerationTarget.JavaInterop1)
56-
m.Attributes.Add(newIntDefinitionAttr(managedMember,StripExtraInterfaceSpec(member.JavaSignature)));
58+
m.Attributes.Add(newIntDefinitionAttr(managedMemberName,StripExtraInterfaceSpec(member.JavaSignature)));
59+
60+
SourceWriterExtensions.AddSupportedOSPlatform(m.Attributes,member.ApiLevel,opt);
61+
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)
66+
SourceWriterExtensions.AddObsolete(m.Attributes,managedMember.Value.Field.DeprecatedComment,opt,deprecatedSince:managedMember.Value.Field.DeprecatedSince);
5767

5868
enoom.Members.Add(m);
5969
}
6070

6171
returnenoom;
6272
}
6373

64-
stringFindManagedMember(EnumDescriptiondesc,ConstantEntrymember,IEnumerable<GenBase>gens)
74+
WeakReferencecache_found_class;
75+
76+
(GenBaseCls,FieldField)?FindManagedMember(EnumDescriptiondesc,ConstantEntryconstant,IEnumerable<GenBase>gens)
6577
{
6678
if(desc.FieldsRemoved)
6779
returnnull;
6880

69-
varjniMember=member.JavaSignature;
81+
varjniMember=constant.JavaSignature;
82+
7083
if(string.IsNullOrWhiteSpace(jniMember)){
7184
// enum values like "None" falls here.
7285
returnnull;
7386
}
74-
returnFindManagedMember(jniMember,gens);
75-
}
7687

77-
WeakReferencecache_found_class;
88+
ParseJniMember(jniMember,outvarpackage,outvartype,outvarmember);
7889

79-
stringFindManagedMember(stringjniMember,IEnumerable<GenBase>gens)
80-
{
81-
stringpackage,type,member;
82-
ParseJniMember(jniMember,outpackage,outtype,outmember);
8390
varfullJavaType=(string.IsNullOrEmpty(package)?"":package+".")+type;
8491

8592
varcls=cache_found_class!=null?cache_found_class.TargetasGenBase:null;
@@ -96,7 +103,7 @@ string FindManagedMember (string jniMember, IEnumerable<GenBase> gens)
96103
// The field was not found e.g. removed by metadata fixup.
97104
returnnull;
98105
}
99-
returncls.FullName+"."+fld.Name;
106+
return(cls,fld);
100107
}
101108

102109
internalvoidParseJniMember(stringjniMember,outstringpackage,outstringtype,outstringmember)

‎tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,14 @@ public static void AddParameterListCallArgs (List<string> body, ParameterList pa
293293
}
294294

295295
publicstaticvoidAddSupportedOSPlatform(List<AttributeWriter>attributes,ApiVersionsSupport.IApiAvailabilitymember,CodeGenerationOptionsopt)
296+
=>AddSupportedOSPlatform(attributes,member.ApiAvailableSince,opt);
297+
298+
publicstaticvoidAddSupportedOSPlatform(List<AttributeWriter>attributes,intsince,CodeGenerationOptionsopt)
296299
{
297300
// There's no sense in writing say 'android15' because we do not support older APIs,
298301
// so those APIs will be available in all of our versions.
299-
if(member.ApiAvailableSince>21&&opt.CodeGenerationTarget==Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1)
300-
attributes.Add(newSupportedOSPlatformAttr(member.ApiAvailableSince));
301-
302+
if(since>21&&opt.CodeGenerationTarget==Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1)
303+
attributes.Add(newSupportedOSPlatformAttr(since));
302304
}
303305

304306
publicstaticvoidAddObsolete(List<AttributeWriter>attributes,stringmessage,CodeGenerationOptionsopt,boolforceDeprecate=false,boolisError=false,int?deprecatedSince=null)

0 commit comments

Comments
 (0)