1 // 2 // The main differences with mono-api-diff are: 3 // * this tool directly produce HTML similar to gdiff.sh used for Xamarin.iOS 4 // * this tool reports changes in an "evolutionary" way, not in a breaking way, 5 // i.e. it does not assume the source assembly is right (but simply older) 6 // * the diff .xml output was not easy to convert back into the HTML format 7 // that gdiff.sh produced 8 // 9 // Authors 10 // Sebastien Pouliot <sebastien@xamarin.com> 11 // 12 // Copyright 2013-2014 Xamarin Inc. http://www.xamarin.com 13 // 14 // Permission is hereby granted, free of charge, to any person obtaining 15 // a copy of this software and associated documentation files (the 16 // "Software"), to deal in the Software without restriction, including 17 // without limitation the rights to use, copy, modify, merge, publish, 18 // distribute, sublicense, and/or sell copies of the Software, and to 19 // permit persons to whom the Software is furnished to do so, subject to 20 // the following conditions: 21 // 22 // The above copyright notice and this permission notice shall be 23 // included in all copies or substantial portions of the Software. 24 // 25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 // 33 34 using System; 35 using System.IO; 36 using System.Collections.Generic; 37 using System.Text.RegularExpressions; 38 39 using Mono.Options; 40 41 namespace Xamarin.ApiDiff { 42 43 public static class State { 44 static TextWriter output; 45 46 public static TextWriter Output { 47 get { 48 if (output == null) 49 output = Console.Out; 50 return output; 51 } 52 set { output = value; } 53 } 54 55 public static string Assembly { get; set; } 56 public static string Namespace { get; set; } 57 public static string Type { get; set; } 58 public static string BaseType { get; set; } 59 60 public static int Indent { get; set; } 61 62 static List<Regex> ignoreAdded = new List<Regex> (); 63 public static List<Regex> IgnoreAdded { 64 get { return ignoreAdded; } 65 } 66 67 static List<Regex> ignoreNew = new List<Regex> (); 68 public static List<Regex> IgnoreNew { 69 get { return ignoreNew; } 70 } 71 72 static List<Regex> ignoreRemoved = new List<Regex> (); 73 public static List<Regex> IgnoreRemoved { 74 get { return ignoreRemoved; } 75 } 76 77 public static bool IgnoreParameterNameChanges { get; set; } 78 public static bool IgnoreVirtualChanges { get; set; } 79 public static bool IgnoreAddedPropertySetters { get; set; } 80 81 public static bool IgnoreNonbreaking { get; set; } 82 83 public static bool Lax; 84 public static bool Colorize = true; 85 86 public static int Verbosity; 87 LogDebugMessage(string value)88 public static void LogDebugMessage (string value) 89 { 90 if (Verbosity == 0) 91 return; 92 Console.WriteLine (value); 93 } 94 } 95 class Program { 96 Main(string[] args)97 public static int Main (string[] args) 98 { 99 var showHelp = false; 100 string diff = null; 101 List<string> extra = null; 102 103 var options = new OptionSet { 104 { "h|help", "Show this help", v => showHelp = true }, 105 { "d|diff=", "HTML diff file out output (omit for stdout)", v => diff = v }, 106 { "i|ignore=", "Ignore new, added, and removed members whose description matches a given C# regular expression (see below).", 107 v => { 108 var r = new Regex (v); 109 State.IgnoreAdded.Add (r); 110 State.IgnoreRemoved.Add (r); 111 State.IgnoreNew.Add (r); 112 } 113 }, 114 { "a|ignore-added=", "Ignore added members whose description matches a given C# regular expression (see below).", 115 v => State.IgnoreAdded.Add (new Regex (v)) 116 }, 117 { "r|ignore-removed=", "Ignore removed members whose description matches a given C# regular expression (see below).", 118 v => State.IgnoreRemoved.Add (new Regex (v)) 119 }, 120 { "n|ignore-new=", "Ignore new namespaces and types whose description matches a given C# regular expression (see below).", 121 v => State.IgnoreNew.Add (new Regex (v)) 122 }, 123 { "ignore-changes-parameter-names", "Ignore changes to parameter names for identically prototyped methods.", 124 v => State.IgnoreParameterNameChanges = v != null 125 }, 126 { "ignore-changes-property-setters", "Ignore adding setters to properties.", 127 v => State.IgnoreAddedPropertySetters = v != null 128 }, 129 { "ignore-changes-virtual", "Ignore changing non-`virtual` to `virtual` or adding `override`.", 130 v => State.IgnoreVirtualChanges = v != null 131 }, 132 { "c|colorize:", "Colorize HTML output", v => State.Colorize = string.IsNullOrEmpty (v) ? true : bool.Parse (v) }, 133 { "x|lax", "Ignore duplicate XML entries", v => State.Lax = true }, 134 { "ignore-nonbreaking", "Ignore all nonbreaking changes", v => State.IgnoreNonbreaking = true }, 135 { "v|verbose:", "Verbosity level; when set, will print debug messages", 136 (int? v) => State.Verbosity = v ?? (State.Verbosity + 1)}, 137 new ResponseFileSource (), 138 }; 139 140 try { 141 extra = options.Parse (args); 142 } catch (OptionException e) { 143 Console.WriteLine ("Option error: {0}", e.Message); 144 showHelp = true; 145 } 146 147 if (State.IgnoreNonbreaking) { 148 State.IgnoreAddedPropertySetters = true; 149 State.IgnoreVirtualChanges = true; 150 State.IgnoreNew.Add (new Regex (".*")); 151 State.IgnoreAdded.Add (new Regex (".*")); 152 } 153 154 if (showHelp || extra == null || extra.Count < 2 || extra.Count > 3) { 155 Console.WriteLine (@"Usage: mono-api-html [options] <reference.xml> <assembly.xml> [diff.html]"); 156 Console.WriteLine (); 157 Console.WriteLine ("Available options:"); 158 options.WriteOptionDescriptions (Console.Out); 159 Console.WriteLine (); 160 Console.WriteLine ("Ignoring Members:"); 161 Console.WriteLine (); 162 Console.WriteLine (" Members that were added can be filtered out of the diff by using the"); 163 Console.WriteLine (" -i, --ignore-added option. The option takes a C# regular expression"); 164 Console.WriteLine (" to match against member descriptions. For example, to ignore the"); 165 Console.WriteLine (" introduction of the interfaces 'INSCopying' and 'INSCoding' on types"); 166 Console.WriteLine (" pass the following to mono-api-html:"); 167 Console.WriteLine (); 168 Console.WriteLine (" mono-api-html ... -i 'INSCopying$' -i 'INSCoding$'"); 169 Console.WriteLine (); 170 Console.WriteLine (" The regular expressions will match any member description ending with"); 171 Console.WriteLine (" 'INSCopying' or 'INSCoding'."); 172 Console.WriteLine (); 173 return 1; 174 } 175 176 var input = extra [0]; 177 var output = extra [1]; 178 if (extra.Count == 3 && diff == null) 179 diff = extra [2]; 180 181 try { 182 var ac = new AssemblyComparer (input, output); 183 if (diff != null) { 184 string diffHtml = String.Empty; 185 using (var writer = new StringWriter ()) { 186 State.Output = writer; 187 ac.Compare (); 188 diffHtml = State.Output.ToString (); 189 } 190 if (diffHtml.Length > 0) { 191 using (var file = new StreamWriter (diff)) { 192 file.WriteLine ("<div>"); 193 if (State.Colorize) { 194 file.WriteLine ("<style scoped>"); 195 file.WriteLine ("\t.obsolete { color: gray; }"); 196 file.WriteLine ("\t.added { color: green; }"); 197 file.WriteLine ("\t.removed-inline { text-decoration: line-through; }"); 198 file.WriteLine ("\t.removed-breaking-inline { color: red;}"); 199 file.WriteLine ("\t.added-breaking-inline { text-decoration: underline; }"); 200 file.WriteLine ("\t.nonbreaking { color: black; }"); 201 file.WriteLine ("\t.breaking { color: red; }"); 202 file.WriteLine ("</style>"); 203 } 204 file.WriteLine ( 205 @"<script type=""text/javascript""> 206 // Only some elements have 'data-is-[non-]breaking' attributes. Here we 207 // iterate over all descendents elements, and set 'data-is-[non-]breaking' 208 // depending on whether there are any descendents with that attribute. 209 function propagateDataAttribute (element) 210 { 211 if (element.hasAttribute ('data-is-propagated')) 212 return; 213 214 var i; 215 var any_breaking = element.hasAttribute ('data-is-breaking'); 216 var any_non_breaking = element.hasAttribute ('data-is-non-breaking'); 217 for (i = 0; i < element.children.length; i++) { 218 var el = element.children [i]; 219 propagateDataAttribute (el); 220 any_breaking |= el.hasAttribute ('data-is-breaking'); 221 any_non_breaking |= el.hasAttribute ('data-is-non-breaking'); 222 } 223 224 if (any_breaking) 225 element.setAttribute ('data-is-breaking', null); 226 else if (any_non_breaking) 227 element.setAttribute ('data-is-non-breaking', null); 228 element.setAttribute ('data-is-propagated', null); 229 } 230 231 function hideNonBreakingChanges () 232 { 233 var topNodes = document.querySelectorAll ('[data-is-topmost]'); 234 var n; 235 var i; 236 for (n = 0; n < topNodes.length; n++) { 237 propagateDataAttribute (topNodes [n]); 238 var elements = topNodes [n].querySelectorAll ('[data-is-non-breaking]'); 239 for (i = 0; i < elements.length; i++) { 240 var el = elements [i]; 241 if (!el.hasAttribute ('data-original-display')) 242 el.setAttribute ('data-original-display', el.style.display); 243 el.style.display = 'none'; 244 } 245 } 246 247 var links = document.getElementsByClassName ('hide-nonbreaking'); 248 for (i = 0; i < links.length; i++) 249 links [i].style.display = 'none'; 250 links = document.getElementsByClassName ('restore-nonbreaking'); 251 for (i = 0; i < links.length; i++) 252 links [i].style.display = ''; 253 } 254 255 function showNonBreakingChanges () 256 { 257 var elements = document.querySelectorAll ('[data-original-display]'); 258 var i; 259 for (i = 0; i < elements.length; i++) { 260 var el = elements [i]; 261 el.style.display = el.getAttribute ('data-original-display'); 262 } 263 264 var links = document.getElementsByClassName ('hide-nonbreaking'); 265 for (i = 0; i < links.length; i++) 266 links [i].style.display = ''; 267 links = document.getElementsByClassName ('restore-nonbreaking'); 268 for (i = 0; i < links.length; i++) 269 links [i].style.display = 'none'; 270 } 271 </script>"); 272 if (ac.SourceAssembly == ac.TargetAssembly) { 273 file.WriteLine ("<h1>{0}.dll</h1>", ac.SourceAssembly); 274 } else { 275 file.WriteLine ("<h1>{0}.dll vs {1}.dll</h1>", ac.SourceAssembly, ac.TargetAssembly); 276 } 277 if (!State.IgnoreNonbreaking) { 278 file.WriteLine ("<a href='javascript: hideNonBreakingChanges (); ' class='hide-nonbreaking'>Hide non-breaking changes</a>"); 279 file.WriteLine ("<a href='javascript: showNonBreakingChanges (); ' class='restore-nonbreaking' style='display: none;'>Show non-breaking changes</a>"); 280 file.WriteLine ("<br/>"); 281 } 282 file.WriteLine ("<div data-is-topmost>"); 283 file.Write (diffHtml); 284 file.WriteLine ("</div> <!-- end topmost div -->"); 285 file.WriteLine ("</div>"); 286 } 287 } 288 } else { 289 State.Output = Console.Out; 290 ac.Compare (); 291 } 292 } 293 catch (Exception e) { 294 Console.WriteLine (e); 295 return 1; 296 } 297 return 0; 298 } 299 } 300 } 301