5
5
using System . Runtime . InteropServices ;
6
6
using Semmle . Util ;
7
7
using Semmle . Extraction . CSharp . Standalone ;
8
+ using System . Threading . Tasks ;
9
+ using System . Collections . Concurrent ;
8
10
9
11
namespace Semmle . BuildAnalyser
10
12
{
@@ -56,6 +58,7 @@ class BuildAnalysis : IBuildAnalysis
56
58
int failedProjects , succeededProjects ;
57
59
readonly string [ ] allSources ;
58
60
int conflictedReferences = 0 ;
61
+ object mutex = new object ( ) ;
59
62
60
63
/// <summary>
61
64
/// Performs a C# build analysis.
@@ -64,6 +67,8 @@ class BuildAnalysis : IBuildAnalysis
64
67
/// <param name="progress">Display of analysis progress.</param>
65
68
public BuildAnalysis ( Options options , IProgressMonitor progress )
66
69
{
70
+ var startTime = DateTime . Now ;
71
+
67
72
progressMonitor = progress ;
68
73
sourceDir = new DirectoryInfo ( options . SrcDir ) ;
69
74
@@ -74,38 +79,51 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
74
79
Where ( d => ! options . ExcludesFile ( d ) ) .
75
80
ToArray ( ) ;
76
81
77
- var dllDirNames = options . DllDirs . Select ( Path . GetFullPath ) ;
82
+ var dllDirNames = options . DllDirs . Select ( Path . GetFullPath ) . ToList ( ) ;
83
+ PackageDirectory = TemporaryDirectory . CreateTempDirectory ( sourceDir . FullName , progressMonitor ) ;
78
84
79
85
if ( options . UseNuGet )
80
86
{
81
- nuget = new NugetPackages ( sourceDir . FullName ) ;
82
- ReadNugetFiles ( ) ;
83
- dllDirNames = dllDirNames . Concat ( Enumerators . Singleton ( nuget . PackageDirectory ) ) ;
87
+ try
88
+ {
89
+ nuget = new NugetPackages ( sourceDir . FullName , PackageDirectory ) ;
90
+ ReadNugetFiles ( ) ;
91
+ }
92
+ catch ( FileNotFoundException )
93
+ {
94
+ progressMonitor . MissingNuGet ( ) ;
95
+ }
84
96
}
85
97
86
98
// Find DLLs in the .Net Framework
87
99
if ( options . ScanNetFrameworkDlls )
88
100
{
89
- dllDirNames = dllDirNames . Concat ( Runtime . Runtimes . Take ( 1 ) ) ;
101
+ dllDirNames . Add ( Runtime . Runtimes . First ( ) ) ;
90
102
}
103
+
104
+ {
105
+ using var renamer1 = new FileRenamer ( sourceDir . GetFiles ( "global.json" , SearchOption . AllDirectories ) ) ;
106
+ using var renamer2 = new FileRenamer ( sourceDir . GetFiles ( "Directory.Build.props" , SearchOption . AllDirectories ) ) ;
91
107
92
- assemblyCache = new BuildAnalyser . AssemblyCache ( dllDirNames , progress ) ;
108
+ var solutions = options . SolutionFile != null ?
109
+ new [ ] { options . SolutionFile } :
110
+ sourceDir . GetFiles ( "*.sln" , SearchOption . AllDirectories ) . Select ( d => d . FullName ) ;
93
111
94
- // Analyse all .csproj files in the source tree.
95
- if ( options . SolutionFile != null )
96
- {
97
- AnalyseSolution ( options . SolutionFile ) ;
98
- }
99
- else if ( options . AnalyseCsProjFiles )
100
- {
101
- AnalyseProjectFiles ( ) ;
112
+
113
+ RestoreSolutions ( solutions ) ;
114
+ dllDirNames . Add ( PackageDirectory . DirInfo . FullName ) ;
115
+ assemblyCache = new BuildAnalyser . AssemblyCache ( dllDirNames , progress ) ;
116
+ AnalyseSolutions ( solutions ) ;
117
+
118
+ usedReferences = new HashSet < string > ( assemblyCache . AllAssemblies . Select ( a => a . Filename ) ) ;
102
119
}
103
120
104
121
if ( ! options . AnalyseCsProjFiles )
105
122
{
106
123
usedReferences = new HashSet < string > ( assemblyCache . AllAssemblies . Select ( a => a . Filename ) ) ;
107
124
}
108
125
126
+
109
127
ResolveConflicts ( ) ;
110
128
111
129
if ( options . UseMscorlib )
@@ -133,6 +151,8 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
133
151
conflictedReferences ,
134
152
succeededProjects + failedProjects ,
135
153
failedProjects ) ;
154
+
155
+ Console . WriteLine ( $ "Build analysis completed in { DateTime . Now - startTime } ") ;
136
156
}
137
157
138
158
/// <summary>
@@ -183,7 +203,8 @@ void ReadNugetFiles()
183
203
/// <param name="reference">The filename of the reference.</param>
184
204
void UseReference ( string reference )
185
205
{
186
- usedReferences . Add ( reference ) ;
206
+ lock ( mutex )
207
+ usedReferences . Add ( reference ) ;
187
208
}
188
209
189
210
/// <summary>
@@ -194,11 +215,13 @@ void UseSource(FileInfo sourceFile)
194
215
{
195
216
if ( sourceFile . Exists )
196
217
{
197
- usedSources . Add ( sourceFile . FullName ) ;
218
+ lock ( mutex )
219
+ usedSources . Add ( sourceFile . FullName ) ;
198
220
}
199
221
else
200
222
{
201
- missingSources . Add ( sourceFile . FullName ) ;
223
+ lock ( mutex )
224
+ missingSources . Add ( sourceFile . FullName ) ;
202
225
}
203
226
}
204
227
@@ -236,77 +259,100 @@ void UseSource(FileInfo sourceFile)
236
259
/// <param name="projectFile">The project file making the reference.</param>
237
260
void UnresolvedReference ( string id , string projectFile )
238
261
{
239
- unresolvedReferences [ id ] = projectFile ;
262
+ lock ( mutex )
263
+ unresolvedReferences [ id ] = projectFile ;
240
264
}
241
265
242
- /// <summary>
243
- /// Performs an analysis of all .csproj files.
244
- /// </summary>
245
- void AnalyseProjectFiles ( )
246
- {
247
- AnalyseProjectFiles ( sourceDir . GetFiles ( "*.csproj" , SearchOption . AllDirectories ) ) ;
248
- }
266
+ TemporaryDirectory PackageDirectory ;
249
267
250
268
/// <summary>
251
269
/// Reads all the source files and references from the given list of projects.
252
270
/// </summary>
253
271
/// <param name="projectFiles">The list of projects to analyse.</param>
254
- void AnalyseProjectFiles ( FileInfo [ ] projectFiles )
272
+ void AnalyseProjectFiles ( IEnumerable < FileInfo > projectFiles )
255
273
{
256
- progressMonitor . AnalysingProjectFiles ( projectFiles . Count ( ) ) ;
257
-
258
274
foreach ( var proj in projectFiles )
275
+ AnalyseProject ( proj ) ;
276
+ }
277
+
278
+ void AnalyseProject ( FileInfo project )
279
+ {
280
+ if ( ! project . Exists )
259
281
{
260
- try
261
- {
262
- var csProj = new CsProjFile ( proj ) ;
282
+ progressMonitor . MissingProject ( project . FullName ) ;
283
+ return ;
284
+ }
285
+
286
+ try
287
+ {
288
+ var csProj = new CsProjFile ( project ) ;
263
289
264
- foreach ( var @ref in csProj . References )
290
+ foreach ( var @ref in csProj . References )
291
+ {
292
+ AssemblyInfo resolved = assemblyCache . ResolveReference ( @ref ) ;
293
+ if ( ! resolved . Valid )
265
294
{
266
- AssemblyInfo resolved = assemblyCache . ResolveReference ( @ref ) ;
267
- if ( ! resolved . Valid )
268
- {
269
- UnresolvedReference ( @ref , proj . FullName ) ;
270
- }
271
- else
272
- {
273
- UseReference ( resolved . Filename ) ;
274
- }
295
+ UnresolvedReference ( @ref , project . FullName ) ;
275
296
}
276
-
277
- foreach ( var src in csProj . Sources )
297
+ else
278
298
{
279
- // Make a note of which source files the projects use.
280
- // This information doesn't affect the build but is dumped
281
- // as diagnostic output.
282
- UseSource ( new FileInfo ( src ) ) ;
299
+ UseReference ( resolved . Filename ) ;
283
300
}
284
- ++ succeededProjects ;
285
301
}
286
- catch ( Exception ex ) // lgtm[cs/catch-of-all-exceptions]
302
+
303
+ foreach ( var src in csProj . Sources )
287
304
{
288
- ++ failedProjects ;
289
- progressMonitor . FailedProjectFile ( proj . FullName , ex . Message ) ;
305
+ // Make a note of which source files the projects use.
306
+ // This information doesn't affect the build but is dumped
307
+ // as diagnostic output.
308
+ UseSource ( new FileInfo ( src ) ) ;
290
309
}
310
+
311
+ ++ succeededProjects ;
312
+ }
313
+ catch ( Exception ex ) // lgtm[cs/catch-of-all-exceptions]
314
+ {
315
+ ++ failedProjects ;
316
+ progressMonitor . FailedProjectFile ( project . FullName , ex . Message ) ;
291
317
}
318
+
292
319
}
293
320
294
321
/// <summary>
295
322
/// Delete packages directory.
296
323
/// </summary>
297
324
public void Cleanup ( )
298
325
{
299
- if ( nuget != null ) nuget . Cleanup ( progressMonitor ) ;
326
+ PackageDirectory ? . Cleanup ( ) ;
300
327
}
301
328
302
- /// <summary>
303
- /// Analyse all project files in a given solution only.
304
- /// </summary>
305
- /// <param name="solutionFile">The filename of the solution.</param>
306
- public void AnalyseSolution ( string solutionFile )
329
+ void Restore ( string projectOrSolution )
307
330
{
308
- var sln = new SolutionFile ( solutionFile ) ;
309
- AnalyseProjectFiles ( sln . Projects . Select ( p => new FileInfo ( p ) ) . ToArray ( ) ) ;
331
+ int exit = DotNet . RestoreToDirectory ( projectOrSolution , PackageDirectory . DirInfo . FullName ) ;
332
+ if ( exit != 0 )
333
+ progressMonitor . CommandFailed ( "dotnet" , $ "restore \" { projectOrSolution } \" ", exit ) ;
334
+ }
335
+
336
+ public void RestoreSolutions ( IEnumerable < string > solutions )
337
+ {
338
+ Parallel . ForEach ( solutions , new ParallelOptions { MaxDegreeOfParallelism = 4 } , Restore ) ;
339
+ }
340
+
341
+ public void AnalyseSolutions ( IEnumerable < string > solutions )
342
+ {
343
+ Parallel . ForEach ( solutions , new ParallelOptions { MaxDegreeOfParallelism = 4 } , solutionFile =>
344
+ {
345
+ try
346
+ {
347
+ var sln = new SolutionFile ( solutionFile ) ;
348
+ progressMonitor . AnalysingSolution ( solutionFile ) ;
349
+ AnalyseProjectFiles ( sln . Projects . Select ( p => new FileInfo ( p ) ) . Where ( p => p . Exists ) . ToArray ( ) ) ;
350
+ }
351
+ catch ( Microsoft . Build . Exceptions . InvalidProjectFileException ex )
352
+ {
353
+ progressMonitor . FailedProjectFile ( solutionFile , ex . BaseMessage ) ;
354
+ }
355
+ } ) ;
310
356
}
311
357
}
312
358
}
0 commit comments