// // MoonlightA11yDescriptorGenerator.cs // // Author: // Andrés G. Aragoneses (aaragoneses@novell.com) // // (C) 2009 Novell, Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Text; using System.Xml; using System.Xml.XPath; using Mono.Linker; using Mono.Linker.Steps; using Mono.Cecil; namespace Mono.Tuner { public class MoonlightA11yDescriptorGenerator : BaseStep { XmlTextWriter writer = null; protected override void ProcessAssembly (AssemblyDefinition assembly) { if (assembly.Name.Name == "MoonAtkBridge" || assembly.Name.Name == "System.Windows" || assembly.Name.Name.Contains ("Dummy")) return; if (writer == null) { if (!Directory.Exists (Context.OutputDirectory)) Directory.CreateDirectory (Context.OutputDirectory); string file_name = "descriptors.xml"; string file_path = Path.Combine (Context.OutputDirectory, file_name); if (File.Exists (file_path)) File.Delete (file_path); FileStream xml_file = new FileStream (file_path, FileMode.OpenOrCreate); Console.WriteLine ("Created file {0}", file_name); Console.Write ("Writing contents..."); writer = new XmlTextWriter (xml_file, System.Text.Encoding.UTF8); writer.Formatting = Formatting.Indented; writer.WriteStartElement("linker"); } SortedDictionary types = ScanAssembly (assembly); if (types != null && types.Count > 0) { writer.WriteStartElement("assembly"); writer.WriteAttributeString ("fullname", assembly.Name.Name); foreach (TypeDefinition type in types.Keys) { IList members = types [type]; if (members != null && members.Count > 0) { writer.WriteStartElement("type"); writer.WriteAttributeString ("fullname", type.FullName); foreach (IMetadataTokenProvider member in members) { MethodDefinition method = member as MethodDefinition; if (method != null) { writer.WriteStartElement("method"); writer.WriteAttributeString ("signature", method.ReturnType.FullName + " " + method.Name + GetMethodParams (method)); writer.WriteEndElement (); continue; } FieldDefinition field = member as FieldDefinition; if (field != null) { writer.WriteStartElement("field"); writer.WriteAttributeString ("signature", field.DeclaringType.FullName + " " + field.Name); writer.WriteEndElement (); } } writer.WriteEndElement (); } } writer.WriteEndElement (); Console.WriteLine (); } } protected override void EndProcess () { Console.WriteLine (); foreach (FileStream stream in streams) stream.Close (); if (writer != null) { writer.WriteEndElement (); writer.Close (); writer = null; } } //this is almost the ToString method of MethodDefinition... private string GetMethodParams (MethodDefinition method) { string @params = "("; if (method.HasParameters) { for (int i = 0; i < method.Parameters.Count; i++) { if (i > 0) @params += ","; @params += method.Parameters [i].ParameterType.FullName; } } @params += ")"; return @params; } SortedDictionary /*,List>*/ ScanAssembly (AssemblyDefinition assembly) { if (Annotations.GetAction (assembly) != AssemblyAction.Link) return null; SortedDictionary members_used = new SortedDictionary (new TypeComparer ()); foreach (TypeDefinition type in assembly.MainModule.Types) { IList used_providers = FilterPublicMembers (ScanType (type)); if (used_providers.Count > 0) members_used [type] = used_providers; else if (IsInternal (type, true) && Annotations.IsMarked (type)) throw new NotSupportedException (String.Format ("The type {0} is used while its API is not", type.ToString ())); } return members_used; } IList ScanType (TypeDefinition type) { return ExtractUsedProviders (type.Methods, type.Fields); } static IList FilterPublicMembers (IList members) { IList new_list = new ArrayList (); foreach (MemberReference item in members) if (IsInternal (item, true)) new_list.Add (item); return new_list; } static string [] master_infos = Directory.GetFiles (Environment.CurrentDirectory, "*.info"); static string FindMasterInfoFile (string name) { if (master_infos.Length == 0) throw new Exception ("No masterinfo files found in current directory"); foreach (string file in master_infos) { if (file.EndsWith (name + ".info")) return file; } return null; } const string xpath_init = "assemblies/assembly/namespaces/namespace[@name='{0}']/classes/class[@name='{1}']"; static string GetXPathSearchForType (TypeDefinition type) { TypeDefinition parent_type = type; string xpath = String.Empty; while (parent_type.DeclaringType != null) { xpath = String.Format ("/classes/class[@name='{0}']", parent_type.Name) + xpath; parent_type = parent_type.DeclaringType; } return String.Format (xpath_init, parent_type.Namespace, parent_type.Name) + xpath; } static bool IsInternal (MemberReference member, bool master_info) { TypeDefinition type = null; string master_info_file = null; if (member is TypeDefinition) { type = member as TypeDefinition; if (!master_info) return (!type.IsNested && !type.IsPublic) || (type.IsNested && (!type.IsNestedPublic || IsInternal (type.DeclaringType, false))); master_info_file = FindMasterInfoFile (type.Module.Assembly.Name.Name); if (master_info_file == null) return IsInternal (member, false); return !NodeExists (master_info_file, GetXPathSearchForType (type)); } type = member.DeclaringType.Resolve (); if (IsInternal (type, master_info)) return true; MethodDefinition method = member as MethodDefinition; FieldDefinition field = member as FieldDefinition; if (field == null && method == null) throw new System.NotSupportedException ("Members to scan should be methods or fields"); if (!master_info) { if (method != null) return !method.IsPublic; return !field.IsPublic; } master_info_file = FindMasterInfoFile (type.Module.Assembly.Name.Name); if (master_info_file == null) return IsInternal (member, false); string xpath_type = GetXPathSearchForType (type); string name; if (field != null) name = field.Name; else { name = method.ToString (); //lame, I know... name = WackyOutArgs (WackyCommas (name.Substring (name.IndexOf ("::") + 2) .Replace ("/", "+") // nested classes .Replace ('<', '[').Replace ('>', ']'))); //generic params } if (field != null || !IsPropertyMethod (method)) return !NodeExists (master_info_file, xpath_type + String.Format ("/*/*[@name='{0}']", name)); return !NodeExists (master_info_file, xpath_type + String.Format ("/properties/*/*/*[@name='{0}']", name)); } //at some point I want to get rid of this method and ask cecil's maintainer to spew commas in a uniform way... static string WackyCommas (string method) { string outstring = String.Empty; bool square_bracket = false; foreach (char c in method) { if (c == '[') square_bracket = true; else if (c == ']') square_bracket = false; outstring = outstring + c; if (c == ',' && !square_bracket) outstring = outstring + " "; } return outstring; } //ToString() spews & but not 'out' keyword static string WackyOutArgs (string method) { return Regex.Replace (method, @"\w+&", delegate (Match m) { return "out " + m.ToString (); }); } //copied from MarkStep (violating DRY unless I can put this in a better place... Cecil?) static bool IsPropertyMethod (MethodDefinition md) { return (md.SemanticsAttributes & MethodSemanticsAttributes.Getter) != 0 || (md.SemanticsAttributes & MethodSemanticsAttributes.Setter) != 0; } static Dictionary navs = new Dictionary (); static List streams = new List (); static bool NodeExists (string file, string xpath) { Console.Write ("."); //Console.WriteLine ("Looking for node {0} in file {1}", xpath, file.Substring (file.LastIndexOf ("/") + 1)); XPathNavigator nav = null; if (!navs.TryGetValue (file, out nav)) { FileStream stream = new FileStream (file, FileMode.Open); XPathDocument document = new XPathDocument (stream); nav = document.CreateNavigator (); streams.Add (stream); navs [file] = nav; } return nav.SelectSingleNode (xpath) != null; } IList /*List*/ ExtractUsedProviders (params IList[] members) { IList used = new ArrayList (); if (members == null || members.Length == 0) return used; foreach (IList members_list in members) foreach (IMetadataTokenProvider provider in members_list) if (Annotations.IsMarked (provider)) used.Add (provider); return used; } class TypeComparer : IComparer { public int Compare (TypeDefinition x, TypeDefinition y) { return string.Compare (x.ToString (), y.ToString ()); } } } }