-
Notifications
You must be signed in to change notification settings - Fork 2
/
Comparer.cs
277 lines (247 loc) · 7.9 KB
/
Comparer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
using System.Data;
using System.Text;
using CliWrap;
using SimpleGist;
namespace AppCompare;
class Comparer {
const string filter_all_files = "*.*";
const string filter_obj_files = "*.o";
public static DataTable GetAppCompareTable (string app1path, string app2path, Dictionary<string, string> mappings)
{
DataTable dt = new ("App bundle compare");
DataColumn files = new ("Files", typeof (string));
dt.Columns.Add (files);
dt.PrimaryKey = new [] { files };
dt.Columns.Add (new DataColumn ("Size A", typeof ((FileInfo, long))));
dt.Columns.Add (new DataColumn ("Size B", typeof ((FileInfo, long))));
dt.Columns.Add (new DataColumn ("diff", typeof (long)));
dt.Columns.Add (new DataColumn ("%", typeof (double)));
dt.Columns.Add (new DataColumn (" ", typeof (string)));
try {
Populate (dt, app1path, app2path, mappings, filter_all_files);
} catch (Exception ex) {
dt.ExtendedProperties.Add ("Exception", ex);
}
dt.DefaultView.Sort = "Files ASC";
dt = dt.DefaultView.ToTable ();
dt.ExtendedProperties.Add ("AppA", app1path);
dt.ExtendedProperties.Add ("AppB", app2path);
long size_a = 0;
long size_b = 0;
long managed_a = 0;
long managed_b = 0;
long native_a = 0;
long native_b = 0;
long aotdata_a = 0;
long aotdata_b = 0;
long others_a = 0;
long others_b = 0;
foreach (DataRow row in dt.Rows) {
(FileInfo? f1file, long f1length) = ((FileInfo?, long)) row [1];
size_a += f1length;
(FileInfo? f2file, long f2length) = ((FileInfo?, long)) row [2];
size_b += f2length;
FileInfo f = f1file ?? f2file!;
switch (f.Extension) {
case ".dll":
case ".exe":
managed_a += f1length;
managed_b += f2length;
break;
case "":
switch (f.Name) {
case "CodeResources":
case "PkgInfo":
others_a += f1length;
others_b += f2length;
break;
default:
native_a += f1length;
native_b += f2length;
break;
}
break;
case ".arm64":
case ".armv7":
case ".armv7s":
case ".x86_64":
if (f.Name.Contains (".aotdata.")) {
aotdata_a += f1length;
aotdata_b += f2length;
} else {
others_a += f1length;
others_b += f2length;
}
break;
default:
others_a += f1length;
others_b += f2length;
break;
}
}
AddEmptyRow (dt);
AddEmptyRow (dt, "STATISTICS");
AddSummaryRow (dt, "Native", native_a + aotdata_a, native_b + aotdata_b);
if ((aotdata_a > 0) || (aotdata_b > 0)) {
AddSummaryRow (dt, " Executable", native_a, native_b);
AddSummaryRow (dt, " AOT data", aotdata_a, aotdata_b);
}
if ((managed_a > 0) || (managed_b > 0))
AddSummaryRow (dt, "Managed *.dll/exe", managed_a, managed_b);
AddEmptyRow (dt);
AddSummaryRow (dt, "TOTAL", size_a, size_b);
return dt;
}
public static DataTable GetObjCompareTable (string app1path, string app2path, Dictionary<string, string> mappings)
{
DataTable dt = new ("Object files compare");
DataColumn files = new ("Files", typeof (string));
dt.Columns.Add (files);
dt.PrimaryKey = new [] { files };
dt.Columns.Add (new DataColumn ("Size A", typeof ((FileInfo, long))));
dt.Columns.Add (new DataColumn ("Size B", typeof ((FileInfo, long))));
dt.Columns.Add (new DataColumn ("diff", typeof (long)));
dt.Columns.Add (new DataColumn ("%", typeof (double)));
dt.Columns.Add (new DataColumn (" ", typeof (string)));
try {
Populate (dt, app1path, app2path, mappings, filter_obj_files);
} catch (Exception ex) {
dt.ExtendedProperties.Add ("Exception", ex);
}
dt.DefaultView.Sort = "Files ASC";
dt = dt.DefaultView.ToTable ();
dt.ExtendedProperties.Add ("AppA", app1path);
dt.ExtendedProperties.Add ("AppB", app2path);
AddEmptyRow (dt);
return dt;
}
static void Populate (DataTable dt, string app1path, string app2path, Dictionary<string, string> mappings, string filter)
{
DirectoryInfo Directory1 = new (app1path);
if (Directory1.Exists) {
foreach (var file in Directory1.GetFiles (filter, SearchOption.AllDirectories)) {
dt.Rows.Add (new object? [] {
file.FullName[(app1path.Trim (Path.DirectorySeparatorChar).Length + 2)..],
(file, file.Length),
empty,
-file.Length,
-1.0d,
"",
});
}
}
DirectoryInfo Directory2 = new (app2path);
if (!Directory2.Exists)
return;
foreach (var file in Directory2.GetFiles (filter, SearchOption.AllDirectories)) {
var name = file.FullName[(app2path.Trim (Path.DirectorySeparatorChar).Length + 2)..];
var row = dt.Rows.Find (name);
var remapped = false;
if (row is null) {
// 2nd chance if the name is mapped to a different name in the first directory
if (mappings.TryGetValue (name, out var mapped)) {
row = dt.Rows.Find (mapped);
remapped = true;
}
}
if (row is null) {
dt.Rows.Add (new object? [] {
name,
empty,
(file, file.Length),
file.Length,
double.NaN,
"",
});
} else {
if (remapped) {
var oldname = (string) row [0];
row [0] = oldname + " -> " + name;
}
row [2] = (file, file.Length);
var diff = file.Length;
(_, long f1length) = ((FileInfo?, long)) row [1];
diff -= f1length;
row [3] = diff;
row [4] = diff / (double) f1length;
row [5] = "";
}
}
}
static readonly (FileInfo?, long) empty = (null, 0);
static void AddEmptyRow (DataTable dt, string name = "")
{
dt.Rows.Add (new object [] { name, empty, empty, 0, double.NaN, "" });
}
static void AddSummaryRow (DataTable dt, string name, long a, long b)
{
dt.Rows.Add (new object [] { name,
((FileInfo?) null, a),
((FileInfo?) null, b),
b - a,
(a == 0) ? double.NaN : (b - a) / (double) a,
"",
});
}
static public string ExportMarkdown (List<DataTable> tables)
{
StringBuilder builder = new ();
builder.AppendLine ("# Application Comparer");
builder.AppendLine ();
foreach (var table in tables) {
builder.AppendLine ($"## {table.TableName}");
builder.AppendLine ();
builder.Append ("* Path A: `").Append (table.ExtendedProperties ["AppA"]).AppendLine ("`");
builder.Append ("* Path B: `").Append (table.ExtendedProperties ["AppB"]).AppendLine ("`");
builder.AppendLine ();
var columns = table.Columns;
// skip last "comment" column - it's for the tool itself, not for reporting
for (int i = 0; i < columns.Count - 1; i++) {
builder.Append ("| ").Append (columns [i].ColumnName).Append (' ');
}
builder.AppendLine ("|");
builder.AppendLine ("|:----|----:|----:|----:|----:|");
foreach (DataRow row in table.Rows) {
var file = row [0] as string;
if (file!.Length == 0) {
builder.AppendLine ("| | | | | |");
} else {
builder.Append ("| ").Append (row [0]);
(_, long f1length) = ((FileInfo?, long)) row [1];
builder.Append (" | ").AppendFormat ("{0:N0}", f1length);
(_, long f2length) = ((FileInfo?, long)) row [2];
builder.Append (" | ").AppendFormat ("{0:N0}", f2length);
builder.Append (" | ").AppendFormat ("{0:N0}", row [3]);
var percentage = (double) row [4];
var display = Double.IsNaN (percentage) ? "-" : percentage.ToString ("P1");
builder.Append (" | ").Append (display);
builder.AppendLine (" |)");
}
}
}
builder.AppendLine ();
builder.AppendLine ("Generated by [appcompare](https://github.com/spouliot/appcompare)");
return builder.ToString ();
}
static public string Gist (List<DataTable> tables, bool openUrl = true)
{
GistRequest request = new () {
Description = "Application Comparison Report",
Public = false,
};
request.AddFile ("report.md", ExportMarkdown (tables));
Task<GistResponse>? task = Task.Run (async () => await GistClient.CreateAsync (request));
var url = "https://github.com/spouliot/SimpleGist/wiki#errors";
if (task.Result is null)
return url;
url = task.Result.Url;
if (openUrl) {
_ = Task.Run (async () => await Cli.Wrap ("open")
.WithArguments (url)
.WithValidation (CommandResultValidation.None)
.ExecuteAsync ()
).Result;
}
return url;
}
}