@@ -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