Skip to content

Commit a073d99

Browse files
committed
[api-merge] Import from monodroid/3e934261.
`api-merge` is a tool to support emitting *forward compatible* binding assemblies. Way back in the dark days before Mono for Android 4.2, a binding assembly was only backward compatible: if Mono.Android.dll was targeting API-8, the resulting app could run on API-8 *and later*. The opposite was *not* true: you *might not* be able to use Mono.Android.dll for API-10 and run it on an API-8 device. This is *forward compatibility*, the ability for "a system" to accept "input" intended for a later version [0]. *Java* apps were forward compatible. Mono for Android prior to 4.2 did *not* generate forward compatible apps. The reason why was that a binding assembly directly bound what was in the specified API level [1], so if a member "moved" in a *backward compatible* manner -- moving android.view.MotionEvent.getDeviceId() to the newly introduced android.view.InputEvent base class -- it would not be *forward compatible* for the binding assemblies we emitted, as the e.g. API-10 binding assembly would attempt to resolve the type android.view.InputEvent, which didn't exist in API-8, and would result in an exception. `api-merge` was the fix: instead of a Mono.Android.dll targeting a *specific* API level, it would instead take the API descriptions for the target API level *and all prior API levels*. It would thus have enough information to track member "movements", and with a bit of Java.Interop/tools/generator "magic" forward compatibility was provided starting in Mono for Android 4.2 [2]. [0]: https://en.wikipedia.org/wiki/Forward_compatibility [1]: http://lists.ximian.com/pipermail/monodroid/2011-November/007350.html [2]: https://developer.xamarin.com/releases/android/mono_for_android_4/mono_for_android_4.2/#Improved_API_level_support
1 parent d63c92d commit a073d99

File tree

7 files changed

+1923
-0
lines changed

7 files changed

+1923
-0
lines changed

Xamarin.Android.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Java.Interop.Tools.TypeName
2323
EndProject
2424
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Android", "src\Mono.Android\Mono.Android.csproj", "{66CF299A-CE95-4131-BCD8-DB66E30C4BF7}"
2525
EndProject
26+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "api-merge", "build-tools\api-merge\api-merge.csproj", "{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}"
27+
EndProject
2628
Global
2729
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2830
Debug|AnyCPU = Debug|AnyCPU
@@ -117,6 +119,18 @@ Global
117119
{66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|x86.Build.0 = Debug|Any CPU
118120
{66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|x86.ActiveCfg = Release|Any CPU
119121
{66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|x86.Build.0 = Release|Any CPU
122+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
123+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|AnyCPU.ActiveCfg = Release|Any CPU
124+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|AnyCPU.Build.0 = Release|Any CPU
125+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU
126+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU
127+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|An yCPU
128+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.XAIntegrationRelease|Any CPU.Build.0 = Debug|Any CPU
129+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|x86.ActiveCfg = Debug|Any CPU
130+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|x86.Build.0 = Debug|Any CPU
131+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|x86.ActiveCfg = Release|Any CPU
132+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|x86.Build.0 = Release|Any CPU
133+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|AnyCPU.Build.0 = Debug|Any CPU
120134
EndGlobalSection
121135
GlobalSection(NestedProjects) = preSolution
122136
{8FF78EB6-6FC8-46A7-8A15-EBBA9045C5FA} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62}
@@ -128,6 +142,7 @@ Global
128142
{74598F5C-B8CC-4CE6-8EE2-AB9CA1400076} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
129143
{E706B6F2-5562-4765-8F07-8CF84A797B30} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
130144
{66CF299A-CE95-4131-BCD8-DB66E30C4BF7} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
145+
{3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62}
131146
EndGlobalSection
132147
GlobalSection(MonoDevelopProperties) = preSolution
133148
Policies = $0
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
#define KEEP_OLD_WRONG_COMPATIBILITY
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Xml.Linq;
8+
9+
using Mono.Options;
10+
11+
namespace Xamarin.Android.ApiMerge {
12+
13+
class ApiDescription {
14+
15+
public static bool HackGenericTypeParameterNames = true;
16+
17+
XDocument Contents;
18+
Dictionary<string, XElement> Types = new Dictionary<string, XElement>();
19+
20+
public ApiDescription (string source)
21+
{
22+
Contents = XDocument.Load (source);
23+
foreach (var package in Contents.Element ("api").Elements ("package")) {
24+
AddPackage (package);
25+
}
26+
}
27+
28+
public void Merge (string apiLocation)
29+
{
30+
#if ADD_OBSOLETE_ON_DISAPPEARANCE
31+
var filename = Path.GetFileName (apiLocation);
32+
int apiLevel = int.Parse (filename.Substring (4, filename.IndexOf ('.', 4) - 4));
33+
#endif
34+
var n = XDocument.Load (apiLocation);
35+
foreach (var npackage in n.Elements ("api").Elements ("package")) {
36+
var spackage = GetPackage ((string) npackage.Attribute ("name"));
37+
if (spackage == null) {
38+
AddNewPackage (npackage, apiLocation);
39+
continue;
40+
}
41+
foreach (var ntype in npackage.Elements ()) {
42+
var stype = GetType (spackage, ntype);
43+
if (stype == null) {
44+
AddNewType (spackage, ntype, apiLocation);
45+
continue;
46+
}
47+
foreach (var a in ntype.Attributes ()) {
48+
var sattr = stype.Attribute (a.Name);
49+
switch (a.Name.LocalName) {
50+
#if KEEP_OLD_WRONG_COMPATIBILITY
51+
case "extends":
52+
sattr.Value = a.Value;
53+
break;
54+
case "abstract":
55+
sattr.Value = a.Value;
56+
break;
57+
case "deprecated":
58+
sattr.Value = a.Value;
59+
break;
60+
case "final":
61+
if (a.Value == "false")
62+
sattr.Value = "false";
63+
//sattr.Value = a.Value;
64+
break;
65+
#endif
66+
default:
67+
var sa = stype.Attribute (a.Name);
68+
if (sa == null)
69+
stype.Add (a);
70+
else
71+
sa.SetValue (a.Value);
72+
break;
73+
}
74+
}
75+
#if ADD_OBSOLETE_ON_DISAPPEARANCE
76+
foreach (var smember in stype.Elements ()) {
77+
var nmember = GetMember (ntype, smember);
78+
if (nmember == null) {
79+
var deprecated = smember.Attribute ("deprecated");
80+
if (deprecated != null) {
81+
if (!deprecated.Value.StartsWith ("This member has disappeared"))
82+
deprecated.Value = "This member has disappeared at API Level " + apiLevel;
83+
}
84+
else
85+
smember.Add (new XAttribute (XName.Get ("deprecated"), "This member has disappeared at API Level " + apiLevel));
86+
}
87+
}
88+
#endif
89+
foreach (var nmember in ntype.Elements ()) {
90+
var smember = GetMember (stype, nmember);
91+
if (smember == null) {
92+
AddNewMember (stype, nmember, apiLocation);
93+
continue;
94+
}
95+
if (nmember.Name.LocalName == "field") {
96+
// FIXME: enable this to get the latest field attributes precisely.
97+
/*
98+
foreach (var a in nmember.Attributes ()) {
99+
var sa = smember.Attribute (a.Name);
100+
if (sa == null)
101+
smember.Add (a);
102+
else if (sa.Value != a.Value)
103+
sa.SetValue (a.Value);
104+
}
105+
*/
106+
#if KEEP_OLD_WRONG_COMPATIBILITY
107+
var isDeprecatedS = smember.Attribute ("deprecated");
108+
var isDeprecatedN = nmember.Attribute ("deprecated");
109+
if (isDeprecatedS != null && isDeprecatedN != null)
110+
isDeprecatedS.Value = isDeprecatedN.Value;
111+
#endif
112+
continue;
113+
}
114+
if (nmember.Name.LocalName == "method" || nmember.Name.LocalName == "constructor") {
115+
smember = GetConstructorOrMethod (stype, nmember);
116+
if (smember == null) {
117+
AddNewMember (stype, nmember, apiLocation);
118+
continue;
119+
}
120+
foreach (var a in nmember.Attributes ()) {
121+
var sa = smember.Attribute (a.Name);
122+
if (sa == null)
123+
smember.Add (a);
124+
else if (sa.Value != a.Value)
125+
sa.SetValue (a.Value);
126+
}
127+
#if KEEP_OLD_WRONG_COMPATIBILITY
128+
var isAbstract = smember.Attribute ("abstract");
129+
if (isAbstract != null)
130+
isAbstract.Value = nmember.Attribute ("abstract").Value;
131+
#endif
132+
// This is required to workaround the
133+
// issue that API Level 20 does not offer
134+
// reference docs and we use API Level 21
135+
// docs instead, but it also removed some
136+
// members, resulting in missing parameter
137+
// names (thus p0, p1...).
138+
// For those members, we preserve older
139+
// names on the parameters.
140+
var newNodes = nmember.Elements ("parameter").ToArray ();
141+
var p = newNodes.FirstOrDefault ();
142+
if (p != null && p.Attribute ("name").Value == "p0") {
143+
var oldNodes = smember.Elements ("parameter").ToArray ();
144+
for (int i = 0; i < newNodes.Length; i++)
145+
newNodes [i].Attribute ("name").Value = oldNodes [i].Attribute ("name").Value;
146+
}
147+
// This is required to alter references to old generic argument name to new one.
148+
// e.g. Some<T> in old API, Some<A> in new API and droiddoc -> A should be used.
149+
smember.ReplaceNodes (nmember.Nodes ());
150+
}
151+
}
152+
var tordered = stype.Elements ().OrderBy (e => GetMemberSignature (e)).ToList ();
153+
stype.RemoveNodes ();
154+
stype.Add (tordered);
155+
}
156+
var pordered = spackage.Elements ().OrderBy (e => GetMemberSignature (e)).ToList ();
157+
spackage.RemoveNodes ();
158+
spackage.Add (pordered);
159+
}
160+
}
161+
162+
public void Save (string filename)
163+
{
164+
FixupOverrides ();
165+
if (filename != null)
166+
Contents.Save (filename);
167+
else
168+
Contents.Save (Console.Out);
169+
}
170+
171+
void FixupOverrides ()
172+
{
173+
foreach (var type in Contents.Elements ("api").Elements ("package").Elements ("class")) {
174+
foreach (var method in type.Elements ("method")) {
175+
XElement baseType, sourceType = type;
176+
string extends;
177+
while ((extends = (string) sourceType.Attribute ("extends")) != null &&
178+
Types.TryGetValue (extends, out baseType)) {
179+
sourceType = baseType;
180+
var m = GetConstructorOrMethod (sourceType, method);
181+
if (m == null)
182+
continue;
183+
m.Attribute ("final").Value = "false";
184+
}
185+
}
186+
}
187+
}
188+
189+
void AddPackage (XElement package)
190+
{
191+
foreach (var type in package.Elements ()) {
192+
AddType (type);
193+
}
194+
}
195+
196+
void AddType (XElement type)
197+
{
198+
string package = (string) type.Parent.Attribute ("name");
199+
string typeName = (string) type.Attribute ("name");
200+
string fullName = string.IsNullOrEmpty (package)
201+
? (string) type.Attribute ("name")
202+
: package + "." + typeName;
203+
204+
XElement cur;
205+
if (Types.TryGetValue (fullName, out cur)) {
206+
if (!object.ReferenceEquals (cur, type)) {
207+
Console.Error.WriteLine ("api-merge: warning: Found duplicate type: {0}", fullName);
208+
}
209+
return;
210+
}
211+
Types.Add (fullName, type);
212+
}
213+
214+
XElement AddWithLocation (XElement old, XElement child, string location)
215+
{
216+
child.Add (new XAttribute ("merge.SourceFile", location));
217+
old.Add (child);
218+
return (XElement) old.LastNode;
219+
}
220+
221+
void AddNewPackage (XElement newPackage, string location)
222+
{
223+
AddWithLocation (Contents.Element ("api"), newPackage, location);
224+
}
225+
226+
void AddNewType (XElement sourcePackage, XElement newType, string location)
227+
{
228+
var t = AddWithLocation (sourcePackage, newType, location);
229+
AddType (t);
230+
}
231+
232+
void AddNewMember (XElement sourceType, XElement newMember, string location)
233+
{
234+
AddWithLocation (sourceType, newMember, location);
235+
}
236+
237+
XElement GetPackage (string package)
238+
{
239+
return Contents.Elements ("api").Elements ("package")
240+
.FirstOrDefault (p => ((string) p.Attribute ("name")) == package);
241+
}
242+
243+
XElement GetType (XElement package, XElement type)
244+
{
245+
return package.Elements (type.Name)
246+
.FirstOrDefault (t => ((string) t.Attribute ("name")) == (string) type.Attribute ("name"));
247+
}
248+
249+
XElement GetMember (XElement sourceType, XElement newMember)
250+
{
251+
string newMemberName = (string) newMember.Attribute ("name");
252+
return sourceType.Elements (newMember.Name)
253+
.FirstOrDefault (m => ((string) m.Attribute ("name")) == newMemberName);
254+
}
255+
256+
XElement GetConstructorOrMethod (XElement sourceType, XElement newMember)
257+
{
258+
string name = (string) newMember.Attribute ("name");
259+
return sourceType.Elements (newMember.Name).FirstOrDefault (m => CompareMethods (m, newMember, name));
260+
}
261+
262+
static bool CompareMethods (XElement sourceMethod, XElement newMethod, string name)
263+
{
264+
if (((string) sourceMethod.Attribute ("name")) != name)
265+
return false;
266+
if (sourceMethod.Elements ("parameter").Count() != newMethod.Elements ("parameter").Count ())
267+
return false;
268+
var sourceTypeMappings = GetTypeMappings (sourceMethod);
269+
var newTypeMappings = GetTypeMappings (newMethod);
270+
foreach (var arg in sourceMethod.Elements ("parameter").Zip (newMethod.Elements ("parameter"), (o, n) => new KeyValuePair<XElement, XElement> (o, n))) {
271+
string newType = GetParameterType (arg.Value, newTypeMappings);
272+
string curType = GetParameterType (arg.Key, sourceTypeMappings);
273+
if (curType != newType)
274+
return false;
275+
}
276+
return true;
277+
}
278+
279+
static Dictionary<string, string> GetTypeMappings (XElement method)
280+
{
281+
return method.Parent.Elements ("typeParameters").Elements ("typeParameter")
282+
.ToDictionary (
283+
tp => (string) tp.Attribute ("name"),
284+
tp => tp.Elements ("genericConstraints").Any ()
285+
? (string) tp.Elements ("genericConstraints").Elements ("genericConstraint").First ().Attribute ("type")
286+
: "java.lang.Object"
287+
);
288+
}
289+
290+
static string GetParameterType (XElement parameter, Dictionary<string, string> mappings)
291+
{
292+
string type = (string) parameter.Attribute ("type");
293+
return GetParameterType (type, mappings);
294+
}
295+
296+
const string wildConstraints = "? extends ";
297+
const string superConstraints = "? super ";
298+
static readonly char [] type_sep = new char [] {'<', ',', '?'};
299+
300+
static string GetParameterType (string type, Dictionary<string, string> mappings)
301+
{
302+
if (HackGenericTypeParameterNames && type.Length == 1) // only generic type parameter (name doesn't matter) or obfuscated type (ignorable)
303+
return "*";
304+
305+
// varargs should be normalized.
306+
if (type.EndsWith ("..."))
307+
return GetParameterType (type.Substring (0, type.Length - 3) + "[]", mappings);
308+
309+
int first = type.IndexOfAny (type_sep);
310+
if (first >= 0 && type [first] == '<') {
311+
int last = type.LastIndexOf ('>');
312+
if (last < 0)
313+
throw new ArgumentException (type);
314+
return type.Substring (0, first) + type.Substring (last + 1);
315+
}
316+
317+
if (mappings.ContainsKey (type))
318+
return mappings [type];
319+
return type;
320+
}
321+
322+
static string GetMemberSignature (XElement e)
323+
{
324+
string prefix;
325+
switch (e.Name.LocalName) {
326+
case "typeParameters": prefix = "1|"; break;
327+
case "implements": prefix = "2|"; break;
328+
case "constructor": prefix = "3|"; break;
329+
case "method": prefix = "4|"; break;
330+
case "field": prefix = "5|"; break;
331+
default: prefix = "0|"; break;
332+
}
333+
if (e.Name.LocalName != "method" && e.Name.LocalName != "constructor")
334+
return prefix + (string) e.Attribute ("name");
335+
return prefix +
336+
((string) e.Attribute ("name")) +
337+
"(" +
338+
string.Join (", ", e.Elements ("parameter").Select (p => (string) p.Attribute ("type"))) +
339+
")";
340+
}
341+
}
342+
}

0 commit comments

Comments
 (0)