55using System . Runtime . InteropServices ;
66using Semmle . Util ;
77using Semmle . Extraction . CSharp . Standalone ;
8+ using System . Threading . Tasks ;
9+ using System . Collections . Concurrent ;
810
911namespace Semmle . BuildAnalyser
1012{
@@ -56,6 +58,7 @@ class BuildAnalysis : IBuildAnalysis
5658 int failedProjects , succeededProjects ;
5759 readonly string [ ] allSources ;
5860 int conflictedReferences = 0 ;
61+ object mutex = new object ( ) ;
5962
6063 /// <summary>
6164 /// Performs a C# build analysis.
@@ -64,6 +67,8 @@ class BuildAnalysis : IBuildAnalysis
6467 /// <param name="progress">Display of analysis progress.</param>
6568 public BuildAnalysis ( Options options , IProgressMonitor progress )
6669 {
70+ var startTime = DateTime . Now ;
71+
6772 progressMonitor = progress ;
6873 sourceDir = new DirectoryInfo ( options . SrcDir ) ;
6974
@@ -74,38 +79,51 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
7479 Where ( d => ! options . ExcludesFile ( d ) ) .
7580 ToArray ( ) ;
7681
77- var dllDirNames = options . DllDirs . Select ( Path . GetFullPath ) ;
82+ var dllDirNames = options . DllDirs . Select ( Path . GetFullPath ) . ToList ( ) ;
83+ PackageDirectory = TemporaryDirectory . CreateTempDirectory ( sourceDir . FullName , progressMonitor ) ;
7884
7985 if ( options . UseNuGet )
8086 {
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+ }
8496 }
8597
8698 // Find DLLs in the .Net Framework
8799 if ( options . ScanNetFrameworkDlls )
88100 {
89- dllDirNames = dllDirNames . Concat ( Runtime . Runtimes . Take ( 1 ) ) ;
101+ dllDirNames . Add ( Runtime . Runtimes . First ( ) ) ;
90102 }
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 ) ) ;
91107
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 ) ;
93111
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 ) ) ;
102119 }
103120
104121 if ( ! options . AnalyseCsProjFiles )
105122 {
106123 usedReferences = new HashSet < string > ( assemblyCache . AllAssemblies . Select ( a => a . Filename ) ) ;
107124 }
108125
126+
109127 ResolveConflicts ( ) ;
110128
111129 if ( options . UseMscorlib )
@@ -133,6 +151,8 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
133151 conflictedReferences ,
134152 succeededProjects + failedProjects ,
135153 failedProjects ) ;
154+
155+ Console . WriteLine ( $ "Build analysis completed in { DateTime . Now - startTime } ") ;
136156 }
137157
138158 /// <summary>
@@ -183,7 +203,8 @@ void ReadNugetFiles()
183203 /// <param name="reference">The filename of the reference.</param>
184204 void UseReference ( string reference )
185205 {
186- usedReferences . Add ( reference ) ;
206+ lock ( mutex )
207+ usedReferences . Add ( reference ) ;
187208 }
188209
189210 /// <summary>
@@ -194,11 +215,13 @@ void UseSource(FileInfo sourceFile)
194215 {
195216 if ( sourceFile . Exists )
196217 {
197- usedSources . Add ( sourceFile . FullName ) ;
218+ lock ( mutex )
219+ usedSources . Add ( sourceFile . FullName ) ;
198220 }
199221 else
200222 {
201- missingSources . Add ( sourceFile . FullName ) ;
223+ lock ( mutex )
224+ missingSources . Add ( sourceFile . FullName ) ;
202225 }
203226 }
204227
@@ -236,77 +259,100 @@ void UseSource(FileInfo sourceFile)
236259 /// <param name="projectFile">The project file making the reference.</param>
237260 void UnresolvedReference ( string id , string projectFile )
238261 {
239- unresolvedReferences [ id ] = projectFile ;
262+ lock ( mutex )
263+ unresolvedReferences [ id ] = projectFile ;
240264 }
241265
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 ;
249267
250268 /// <summary>
251269 /// Reads all the source files and references from the given list of projects.
252270 /// </summary>
253271 /// <param name="projectFiles">The list of projects to analyse.</param>
254- void AnalyseProjectFiles ( FileInfo [ ] projectFiles )
272+ void AnalyseProjectFiles ( IEnumerable < FileInfo > projectFiles )
255273 {
256- progressMonitor . AnalysingProjectFiles ( projectFiles . Count ( ) ) ;
257-
258274 foreach ( var proj in projectFiles )
275+ AnalyseProject ( proj ) ;
276+ }
277+
278+ void AnalyseProject ( FileInfo project )
279+ {
280+ if ( ! project . Exists )
259281 {
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 ) ;
263289
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 )
265294 {
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 ) ;
275296 }
276-
277- foreach ( var src in csProj . Sources )
297+ else
278298 {
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 ) ;
283300 }
284- ++ succeededProjects ;
285301 }
286- catch ( Exception ex ) // lgtm[cs/catch-of-all-exceptions]
302+
303+ foreach ( var src in csProj . Sources )
287304 {
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 ) ) ;
290309 }
310+
311+ ++ succeededProjects ;
312+ }
313+ catch ( Exception ex ) // lgtm[cs/catch-of-all-exceptions]
314+ {
315+ ++ failedProjects ;
316+ progressMonitor . FailedProjectFile ( project . FullName , ex . Message ) ;
291317 }
318+
292319 }
293320
294321 /// <summary>
295322 /// Delete packages directory.
296323 /// </summary>
297324 public void Cleanup ( )
298325 {
299- if ( nuget != null ) nuget . Cleanup ( progressMonitor ) ;
326+ PackageDirectory ? . Cleanup ( ) ;
300327 }
301328
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 )
307330 {
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+ } ) ;
310356 }
311357 }
312358}
0 commit comments