8
8
using System . Collections . Concurrent ;
9
9
using System . Text ;
10
10
using System . Security . Cryptography ;
11
+ using System . Text . RegularExpressions ;
11
12
12
13
namespace Semmle . BuildAnalyser
13
14
{
14
15
/// <summary>
15
16
/// Main implementation of the build analysis.
16
17
/// </summary>
17
- internal sealed class BuildAnalysis : IDisposable
18
+ internal sealed partial class BuildAnalysis : IDisposable
18
19
{
19
20
private readonly AssemblyCache assemblyCache ;
20
21
private readonly ProgressMonitor progressMonitor ;
@@ -95,6 +96,7 @@ public BuildAnalysis(Options options, ProgressMonitor progressMonitor)
95
96
{
96
97
Restore ( solutions ) ;
97
98
Restore ( allProjects ) ;
99
+ DownloadMissingPackages ( allProjects ) ;
98
100
}
99
101
}
100
102
@@ -316,9 +318,9 @@ private void AnalyseProject(FileInfo project)
316
318
317
319
}
318
320
319
- private void Restore ( string target )
321
+ private bool Restore ( string target )
320
322
{
321
- dotnet . RestoreToDirectory ( target , packageDirectory . DirInfo . FullName ) ;
323
+ return dotnet . RestoreToDirectory ( target , packageDirectory . DirInfo . FullName ) ;
322
324
}
323
325
324
326
private void Restore ( IEnumerable < string > targets )
@@ -329,6 +331,76 @@ private void Restore(IEnumerable<string> targets)
329
331
}
330
332
}
331
333
334
+ private void DownloadMissingPackages ( IEnumerable < string > restoreTargets )
335
+ {
336
+ var alreadyDownloadedPackages = Directory . GetDirectories ( packageDirectory . DirInfo . FullName ) . Select ( d => Path . GetFileName ( d ) . ToLowerInvariant ( ) ) . ToHashSet ( ) ;
337
+ var notYetDownloadedPackages = new HashSet < string > ( ) ;
338
+
339
+ var allFiles = GetFiles ( "*.*" ) . ToArray ( ) ;
340
+ foreach ( var file in allFiles )
341
+ {
342
+ try
343
+ {
344
+ using var sr = new StreamReader ( file ) ;
345
+ ReadOnlySpan < char > line ;
346
+ while ( ( line = sr . ReadLine ( ) ) != null )
347
+ {
348
+ foreach ( var valueMatch in PackageReference ( ) . EnumerateMatches ( line ) )
349
+ {
350
+ // We can't get the group from the ValueMatch, so doing it manually:
351
+ var match = line . Slice ( valueMatch . Index , valueMatch . Length ) ;
352
+ var includeIndex = match . IndexOf ( "Include" , StringComparison . InvariantCultureIgnoreCase ) ;
353
+ if ( includeIndex == - 1 )
354
+ {
355
+ continue ;
356
+ }
357
+
358
+ match = match . Slice ( includeIndex + "Include" . Length + 1 ) ;
359
+
360
+ var quoteIndex1 = match . IndexOf ( "\" " ) ;
361
+ var quoteIndex2 = match . Slice ( quoteIndex1 + 1 ) . IndexOf ( "\" " ) ;
362
+
363
+ var packageName = match . Slice ( quoteIndex1 + 1 , quoteIndex2 ) . ToString ( ) . ToLowerInvariant ( ) ;
364
+ if ( ! alreadyDownloadedPackages . Contains ( packageName ) )
365
+ {
366
+ notYetDownloadedPackages . Add ( packageName ) ;
367
+ }
368
+ }
369
+ }
370
+ }
371
+ catch ( Exception ex )
372
+ {
373
+ progressMonitor . FailedToReadFile ( file , ex ) ;
374
+ continue ;
375
+ }
376
+ }
377
+
378
+ foreach ( var package in notYetDownloadedPackages )
379
+ {
380
+ progressMonitor . NugetInstall ( package ) ;
381
+ using var tempDir = new TemporaryDirectory ( ComputeTempDirectory ( package ) ) ;
382
+ var success = dotnet . New ( tempDir . DirInfo . FullName ) ;
383
+ if ( ! success )
384
+ {
385
+ continue ;
386
+ }
387
+ success = dotnet . AddPackage ( tempDir . DirInfo . FullName , package ) ;
388
+ if ( ! success )
389
+ {
390
+ continue ;
391
+ }
392
+
393
+ success = Restore ( tempDir . DirInfo . FullName ) ;
394
+
395
+ // TODO: the restore might fail, we could retry with a prerelease (*-* instead of *) version of the package.
396
+
397
+ if ( ! success )
398
+ {
399
+ progressMonitor . FailedToRestoreNugetPackage ( package ) ;
400
+ }
401
+ }
402
+ }
403
+
332
404
private void AnalyseSolutions ( IEnumerable < string > solutions )
333
405
{
334
406
Parallel . ForEach ( solutions , new ParallelOptions { MaxDegreeOfParallelism = 4 } , solutionFile =>
@@ -350,5 +422,8 @@ public void Dispose()
350
422
{
351
423
packageDirectory ? . Dispose ( ) ;
352
424
}
425
+
426
+ [ GeneratedRegex ( "<PackageReference .*Include=\" (.*?)\" .*/>" , RegexOptions . IgnoreCase | RegexOptions . Compiled | RegexOptions . Singleline ) ]
427
+ private static partial Regex PackageReference ( ) ;
353
428
}
354
429
}
0 commit comments