Skip to content

Commit 5220666

Browse files
author
gugavaro
committed
Improving ApiCompat tool
1 parent 1bee4ad commit 5220666

File tree

1 file changed

+84
-31
lines changed

1 file changed

+84
-31
lines changed

build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CheckApiCompatibility.cs

Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public sealed class CheckApiCompatibility : Task
3333
"Mono.Android.dll",
3434
};
3535

36+
static string compatApiCommand = null;
37+
3638
// Path where Microsoft.DotNet.ApiCompat nuget package is located
3739
[Required]
3840
public string ApiCompatPath { get; set; }
@@ -60,7 +62,7 @@ public override bool Execute ()
6062

6163
// Check to see if Api has a previous Api defined.
6264
if (!api_versions.TryGetValue (ApiLevel, out string previousApiLevel)) {
63-
Log.LogError ($"Please add ApiLevel:{ApiLevel} to the list of supported apis.");
65+
LogError ($"Please add ApiLevel:{ApiLevel} to the list of supported apis.");
6466
return !Log.HasLoggedErrors;
6567
}
6668

@@ -88,7 +90,7 @@ public override bool Execute ()
8890
// Check xamarin-android-api-compatibility reference directory exists
8991
var referenceContractPath = Path.Combine (ApiCompatibilityPath, "reference");
9092
if (!Directory.Exists (referenceContractPath)) {
91-
Log.LogMessage (MessageImportance.High, $"CheckApiCompatibility Warning: Skipping reference contract check.\n{referenceContractPath} does not exist.");
93+
Log.LogWarning ($"CheckApiCompatibility Warning: Skipping reference contract check.\n{referenceContractPath} does not exist.");
9294
return !Log.HasLoggedErrors;
9395
}
9496

@@ -126,13 +128,13 @@ void ValidateApiCompat (string contractPath, bool validateAgainstReference)
126128
foreach (var assemblyToValidate in assemblies) {
127129
var contractAssembly = Path.Combine (contractPath, assemblyToValidate);
128130
if (!File.Exists (contractAssembly)) {
129-
Log.LogMessage ($"Contract assembly {assemblyToValidate} does not exists in the contract path.");
131+
Log.LogWarning ($"Contract assembly {assemblyToValidate} does not exists in the contract path.");
130132
continue;
131133
}
132134

133135
var implementationAssembly = Path.Combine (TargetImplementationPath, assemblyToValidate);
134136
if (!File.Exists (implementationAssembly)) {
135-
Log.LogError ($"Implementation assembly {assemblyToValidate} exists in the contract path but not on the implementation folder.");
137+
LogError ($"Implementation assembly {assemblyToValidate} exists in the contract path but not on the implementation folder.");
136138
return;
137139
}
138140

@@ -154,13 +156,60 @@ void ValidateApiCompat (string contractPath, bool validateAgainstReference)
154156
genApiProcess.StartInfo.UseShellExecute = false;
155157
genApiProcess.StartInfo.CreateNoWindow = true;
156158
genApiProcess.StartInfo.RedirectStandardOutput = true;
159+
genApiProcess.StartInfo.RedirectStandardError = true;
160+
genApiProcess.EnableRaisingEvents = true;
161+
162+
var lines = new List<string> ();
163+
var processHasCrashed = false;
164+
void dataReceived (object sender, DataReceivedEventArgs args)
165+
{
166+
if (!string.IsNullOrWhiteSpace (args.Data)) {
167+
lines.Add (args.Data.Trim ());
168+
169+
if (args.Data.IndexOf ("Native Crash Reporting") != -1) {
170+
processHasCrashed = true;
171+
}
172+
}
173+
}
157174

158-
Log.LogMessage (MessageImportance.High, $"CompatApi command: {genApiProcess.StartInfo.FileName} {genApiProcess.StartInfo.Arguments}");
175+
genApiProcess.OutputDataReceived += dataReceived;
176+
genApiProcess.ErrorDataReceived += dataReceived;
159177

160178
// Get api definition for previous Api
161-
genApiProcess.Start ();
162-
ValidateIssues (genApiProcess.StandardOutput, validateAgainstReference);
163-
genApiProcess.WaitForExit ();
179+
for (int i = 0; i < 3; i++) {
180+
lines.Clear ();
181+
processHasCrashed = false;
182+
183+
compatApiCommand = $"CompatApi command: {genApiProcess.StartInfo.FileName} {genApiProcess.StartInfo.Arguments}";
184+
Log.LogMessage (MessageImportance.High, compatApiCommand);
185+
186+
genApiProcess.Start ();
187+
genApiProcess.BeginOutputReadLine ();
188+
genApiProcess.BeginErrorReadLine ();
189+
190+
genApiProcess.WaitForExit ();
191+
192+
genApiProcess.CancelOutputRead ();
193+
genApiProcess.CancelErrorRead ();
194+
195+
if (lines.Count == 0) {
196+
return;
197+
}
198+
199+
if (processHasCrashed) {
200+
if (i + 1 < 3) {
201+
Log.LogWarning ($"Process has crashed.'{Environment.NewLine}Crash report:{Environment.NewLine}{String.Join (Environment.NewLine, lines)}");
202+
Log.LogWarning ($"We will retry.");
203+
continue;
204+
} else {
205+
LogError ($"Unable to get a valid report. Process has crashed.'{Environment.NewLine}Crash report:{Environment.NewLine}{String.Join (Environment.NewLine, lines)}");
206+
return;
207+
}
208+
}
209+
210+
ValidateIssues (lines, validateAgainstReference);
211+
break;
212+
}
164213
}
165214
} finally {
166215
if (Directory.Exists (contractPathDirectory)) {
@@ -174,7 +223,7 @@ void ValidateApiCompat (string contractPath, bool validateAgainstReference)
174223
}
175224

176225
// Validates there is no issue or issues found are acceptable
177-
void ValidateIssues (StreamReader content, bool validateAgainstReference)
226+
void ValidateIssues (IEnumerable<string> content, bool validateAgainstReference)
178227
{
179228
// Load issues into a dictionary
180229
var issuesFound = LoadIssues (content);
@@ -195,31 +244,28 @@ void ValidateIssues (StreamReader content, bool validateAgainstReference)
195244
} else {
196245

197246
// Read and Convert the acceptable issues into a dictionary
198-
using (var streamReader = new StreamReader (acceptableIssuesFile)) {
199-
acceptableIssues = LoadIssues (streamReader);
200-
if (Log.HasLoggedErrors) {
201-
return;
202-
}
247+
var lines = File.ReadAllLines (acceptableIssuesFile);
248+
acceptableIssues = LoadIssues (lines);
249+
if (Log.HasLoggedErrors) {
250+
return;
203251
}
204252
}
205253

206254
// Now remove all acceptable issues form the dictionary of issues found.
207-
var count = 0;
255+
var errors = new List<string> ();
208256
if (acceptableIssues != null) {
209257
foreach (var item in acceptableIssues) {
210258
if (!issuesFound.TryGetValue (item.Key, out HashSet<string> issues)) {
211259
// we should always be able to find the assembly that is reporting the issues
212-
Log.LogMessage (MessageImportance.High, $"There is an invalid assembly listed on the acceptable breakages file: {item.Key}");
213-
count++;
260+
errors.Add ($"There is an invalid assembly listed on the acceptable breakages file: {item.Key}");
214261
continue;
215262
}
216263

217264
foreach (var issue in item.Value) {
218265
// we should always be able to remove the issue, if we try to remove an issue that does not exist,
219266
// it means the acceptable list is incorrect and should be reported.
220267
if (!issues.Remove (issue)) {
221-
Log.LogMessage (MessageImportance.High, $"There is an invalid issue listed on the acceptable breakages file: {issue}");
222-
count++;
268+
errors.Add ($"There is an invalid issue listed on the acceptable breakages file: {issue}");
223269
}
224270
}
225271
}
@@ -231,34 +277,30 @@ void ValidateIssues (StreamReader content, bool validateAgainstReference)
231277
continue;
232278
}
233279

234-
Log.LogMessage (MessageImportance.High, item.Key);
280+
errors.Add (item.Key);
235281
foreach (var issue in item.Value) {
236-
Log.LogMessage (MessageImportance.High, issue);
237-
count++;
282+
errors.Add (issue);
238283
}
239284
}
240285

241-
if (count > 0) {
242-
Log.LogMessage (MessageImportance.High, $"Total Issues: {count}");
243-
Log.LogError ($"CheckApiCompatibility found nonacceptable Api breakages for ApiLevel: {ApiLevel}.");
286+
if (errors.Count > 0) {
287+
errors.Add ($"Total Issues: {errors.Count}");
288+
LogError ($"CheckApiCompatibility found nonacceptable Api breakages for ApiLevel: {ApiLevel}.{Environment.NewLine}{String.Join (Environment.NewLine, errors)}");
244289
}
245290
}
246291

247292
// Converts list of issue into a dictionary
248-
Dictionary<string, HashSet<string>> LoadIssues (StreamReader content)
293+
Dictionary<string, HashSet<string>> LoadIssues (IEnumerable<string> content)
249294
{
250295
var issues = new Dictionary<string, HashSet<string>> ();
251296
HashSet<string> currentSet = null;
252297

253-
while (!content.EndOfStream) {
254-
var line = content.ReadLine ();
298+
foreach (var line in content) {
255299

256300
if (string.IsNullOrWhiteSpace (line) || line.StartsWith ("#")) {
257301
continue;
258302
}
259303

260-
line = line.Trim ();
261-
262304
// Create hashset per assembly
263305
if (line.StartsWith ("Compat issues with assembly", StringComparison.InvariantCultureIgnoreCase)) {
264306
currentSet = new HashSet<string> ();
@@ -273,7 +315,9 @@ Dictionary<string, HashSet<string>> LoadIssues (StreamReader content)
273315

274316
if (currentSet == null) {
275317
// Hashset should never be null, unless exception file is not defining assembly line.
276-
Log.LogError ($"Exception report/file should start with: 'Compat issues with assembly'; was: '{line}'");
318+
// Finish reading stream
319+
var reportContent = Environment.NewLine + "Current content:" + Environment.NewLine + String.Join (Environment.NewLine, content);
320+
LogError ($"Exception report/file should start with: 'Compat issues with assembly ...'{reportContent}");
277321
return null;
278322
}
279323

@@ -283,5 +327,14 @@ Dictionary<string, HashSet<string>> LoadIssues (StreamReader content)
283327

284328
return issues;
285329
}
330+
331+
void LogError(string errorMessage)
332+
{
333+
if (!string.IsNullOrWhiteSpace (compatApiCommand)) {
334+
Log.LogError ($"{compatApiCommand}{Environment.NewLine}{errorMessage}");
335+
} else {
336+
Log.LogError (errorMessage);
337+
}
338+
}
286339
}
287340
}

0 commit comments

Comments
 (0)