@@ -29,26 +29,32 @@ interface IBuildRule
2929 public class Autobuilder
3030 {
3131 /// <summary>
32- /// Full file paths of files found in the project directory.
32+ /// Full file paths of files found in the project directory, as well
33+ /// their distance from the project root folder. The list is sorted
34+ /// by distance in ascending order.
3335 /// </summary>
34- public IEnumerable < string > Paths => pathsLazy . Value ;
35- readonly Lazy < IEnumerable < string > > pathsLazy ;
36+ public IEnumerable < ( string , int ) > Paths => pathsLazy . Value ;
37+ readonly Lazy < IEnumerable < ( string , int ) > > pathsLazy ;
3638
3739 /// <summary>
3840 /// Gets a list of paths matching a set of extensions
39- /// (including the ".").
41+ /// (including the "."), as well their distance from the project root folder.
42+ /// The list is sorted by distance in ascending order.
4043 /// </summary>
4144 /// <param name="extensions">The extensions to find.</param>
4245 /// <returns>The files matching the extension.</returns>
43- public IEnumerable < string > GetExtensions ( params string [ ] extensions ) =>
44- Paths . Where ( p => extensions . Contains ( Path . GetExtension ( p ) ) ) ;
46+ public IEnumerable < ( string , int ) > GetExtensions ( params string [ ] extensions ) =>
47+ Paths . Where ( p => extensions . Contains ( Path . GetExtension ( p . Item1 ) ) ) ;
4548
4649 /// <summary>
47- /// Gets all paths matching a particular filename.
50+ /// Gets all paths matching a particular filename, as well
51+ /// their distance from the project root folder. The list is sorted
52+ /// by distance in ascending order.
4853 /// </summary>
4954 /// <param name="name">The filename to find.</param>
5055 /// <returns>Possibly empty sequence of paths with the given filename.</returns>
51- public IEnumerable < string > GetFilename ( string name ) => Paths . Where ( p => Path . GetFileName ( p ) == name ) ;
56+ public IEnumerable < ( string , int ) > GetFilename ( string name ) =>
57+ Paths . Where ( p => Path . GetFileName ( p . Item1 ) == name ) ;
5258
5359 /// <summary>
5460 /// Holds if a given path, relative to the root of the source directory
@@ -59,30 +65,30 @@ public IEnumerable<string> GetExtensions(params string[] extensions) =>
5965 public bool HasRelativePath ( string path ) => HasPath ( Actions . PathCombine ( RootDirectory , path ) ) ;
6066
6167 /// <summary>
62- /// List of solution files to build.
68+ /// List of project/ solution files to build.
6369 /// </summary>
64- public IList < ISolution > SolutionsToBuild => solutionsToBuildLazy . Value ;
65- readonly Lazy < IList < ISolution > > solutionsToBuildLazy ;
70+ public IList < IProjectOrSolution > ProjectsOrSolutionsToBuild => projectsOrSolutionsToBuildLazy . Value ;
71+ readonly Lazy < IList < IProjectOrSolution > > projectsOrSolutionsToBuildLazy ;
6672
6773 /// <summary>
6874 /// Holds if a given path was found.
6975 /// </summary>
7076 /// <param name="path">The path of the file.</param>
7177 /// <returns>True iff the path was found.</returns>
72- public bool HasPath ( string path ) => Paths . Any ( p => path == p ) ;
78+ public bool HasPath ( string path ) => Paths . Any ( p => path == p . Item1 ) ;
7379
74- void FindFiles ( string dir , int depth , IList < string > results )
80+ void FindFiles ( string dir , int depth , int maxDepth , IList < ( string , int ) > results )
7581 {
7682 foreach ( var f in Actions . EnumerateFiles ( dir ) )
7783 {
78- results . Add ( f ) ;
84+ results . Add ( ( f , depth ) ) ;
7985 }
8086
81- if ( depth > 1 )
87+ if ( depth < maxDepth )
8288 {
8389 foreach ( var d in Actions . EnumerateDirectories ( dir ) )
8490 {
85- FindFiles ( d , depth - 1 , results ) ;
91+ FindFiles ( d , depth + 1 , maxDepth , results ) ;
8692 }
8793 }
8894 }
@@ -113,46 +119,75 @@ public Autobuilder(IBuildActions actions, AutobuildOptions options)
113119 Actions = actions ;
114120 Options = options ;
115121
116- pathsLazy = new Lazy < IEnumerable < string > > ( ( ) =>
122+ pathsLazy = new Lazy < IEnumerable < ( string , int ) > > ( ( ) =>
117123 {
118- var files = new List < string > ( ) ;
119- FindFiles ( options . RootDirectory , options . SearchDepth , files ) ;
120- return files .
121- OrderBy ( s => s . Count ( c => c == Path . DirectorySeparatorChar ) ) .
122- ThenBy ( s => Path . GetFileName ( s ) . Length ) .
123- ToArray ( ) ;
124+ var files = new List < ( string , int ) > ( ) ;
125+ FindFiles ( options . RootDirectory , 0 , options . SearchDepth , files ) ;
126+ return files . OrderBy ( f => f . Item2 ) . ToArray ( ) ;
124127 } ) ;
125128
126- solutionsToBuildLazy = new Lazy < IList < ISolution > > ( ( ) =>
129+ projectsOrSolutionsToBuildLazy = new Lazy < IList < IProjectOrSolution > > ( ( ) =>
127130 {
128131 if ( options . Solution . Any ( ) )
129132 {
130- var ret = new List < ISolution > ( ) ;
133+ var ret = new List < IProjectOrSolution > ( ) ;
131134 foreach ( var solution in options . Solution )
132135 {
133136 if ( actions . FileExists ( solution ) )
134137 ret . Add ( new Solution ( this , solution ) ) ;
135138 else
136- Log ( Severity . Error , "The specified solution file {0 } was not found" , solution ) ;
139+ Log ( Severity . Error , $ "The specified solution file { solution } was not found") ;
137140 }
138141 return ret ;
139142 }
140143
141- var solutions = GetExtensions ( ".sln" ) .
142- Select ( s => new Solution ( this , s ) ) .
143- Where ( s => s . ProjectCount > 0 ) .
144- OrderByDescending ( s => s . ProjectCount ) .
145- ThenBy ( s => s . Path . Length ) .
144+ bool FindFiles ( string extension , Func < string , ProjectOrSolution > create , out IEnumerable < IProjectOrSolution > files )
145+ {
146+ var allFiles = GetExtensions ( extension ) .
147+ Select ( p => ( ProjectOrSolution : create ( p . Item1 ) , DistanceFromRoot : p . Item2 ) ) .
148+ Where ( p => p . ProjectOrSolution . HasLanguage ( this . Options . Language ) ) .
146149 ToArray ( ) ;
147150
148- foreach ( var sln in solutions )
149- {
150- Log ( Severity . Info , $ "Found { sln . Path } with { sln . ProjectCount } { this . Options . Language } projects, version { sln . ToolsVersion } , config { string . Join ( " " , sln . Configurations . Select ( c => c . FullName ) ) } ") ;
151+ if ( allFiles . Length == 0 )
152+ {
153+ files = null ;
154+ return false ;
155+ }
156+
157+ if ( options . AllSolutions )
158+ {
159+ files = allFiles . Select ( p => p . ProjectOrSolution ) ;
160+ return true ;
161+ }
162+
163+ var firstIsClosest = allFiles . Length > 1 && allFiles [ 0 ] . DistanceFromRoot < allFiles [ 1 ] . DistanceFromRoot ;
164+ if ( allFiles . Length == 1 || firstIsClosest )
165+ {
166+ files = allFiles . Select ( p => p . ProjectOrSolution ) . Take ( 1 ) ;
167+ return true ;
168+ }
169+
170+ var candidates = allFiles .
171+ Where ( f => f . DistanceFromRoot == allFiles [ 0 ] . DistanceFromRoot ) .
172+ Select ( f => f . ProjectOrSolution ) ;
173+ Log ( Severity . Info , $ "Found multiple '{ extension } ' files, giving up: { string . Join ( ", " , candidates ) } .") ;
174+ files = new IProjectOrSolution [ 0 ] ;
175+ return true ;
151176 }
152177
153- return new List < ISolution > ( options . AllSolutions ?
154- solutions :
155- solutions . Take ( 1 ) ) ;
178+ // First look for `.proj` files
179+ if ( FindFiles ( ".proj" , f => new Project ( this , f ) , out var ret1 ) )
180+ return ret1 . ToList ( ) ;
181+
182+ // Then look for `.sln` files
183+ if ( FindFiles ( ".sln" , f => new Solution ( this , f ) , out var ret2 ) )
184+ return ret2 . ToList ( ) ;
185+
186+ // Finally look for language specific project files, e.g. `.csproj` files
187+ if ( FindFiles ( this . Options . Language . ProjectExtension , f => new Project ( this , f ) , out var ret3 ) )
188+ return ret3 . ToList ( ) ;
189+
190+ return new List < IProjectOrSolution > ( ) ;
156191 } ) ;
157192
158193 SemmleDist = Actions . GetEnvironmentVariable ( "SEMMLE_DIST" ) ;
0 commit comments