6
6
using System . Collections . Immutable ;
7
7
using System . Collections . ObjectModel ;
8
8
using System . Diagnostics ;
9
+ using System . Diagnostics . CodeAnalysis ;
9
10
using System . Security ;
10
11
using System . Text . Json ;
11
12
using System . Text . Json . Serialization ;
@@ -164,14 +165,26 @@ public VirtualProjectBuildingCommand(
164
165
/// </summary>
165
166
public bool NoWriteBuildMarkers { get ; init ; }
166
167
168
+ private SourceFile EntryPointSourceFile
169
+ {
170
+ get
171
+ {
172
+ if ( field == default )
173
+ {
174
+ field = SourceFile . Load ( EntryPointFileFullPath ) ;
175
+ }
176
+
177
+ return field ;
178
+ }
179
+ }
180
+
167
181
public ImmutableArray < CSharpDirective > Directives
168
182
{
169
183
get
170
184
{
171
185
if ( field . IsDefault )
172
186
{
173
- var sourceFile = SourceFile . Load ( EntryPointFileFullPath ) ;
174
- field = FindDirectives ( sourceFile , reportAllErrors : false , DiagnosticBag . ThrowOnFirst ( ) ) ;
187
+ field = FindDirectives ( EntryPointSourceFile , reportAllErrors : false , DiagnosticBag . ThrowOnFirst ( ) ) ;
175
188
Debug . Assert ( ! field . IsDefault ) ;
176
189
}
177
190
@@ -1047,6 +1060,23 @@ public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection
1047
1060
private ProjectInstance CreateProjectInstance (
1048
1061
ProjectCollection projectCollection ,
1049
1062
Action < IDictionary < string , string > > ? addGlobalProperties )
1063
+ {
1064
+ var project = CreateProjectInstance ( projectCollection , Directives , addGlobalProperties ) ;
1065
+
1066
+ var directives = EvaluateDirectives ( project , Directives , EntryPointSourceFile , DiagnosticBag . ThrowOnFirst ( ) ) ;
1067
+ if ( directives != Directives )
1068
+ {
1069
+ Directives = directives ;
1070
+ project = CreateProjectInstance ( projectCollection , directives , addGlobalProperties ) ;
1071
+ }
1072
+
1073
+ return project ;
1074
+ }
1075
+
1076
+ private ProjectInstance CreateProjectInstance (
1077
+ ProjectCollection projectCollection ,
1078
+ ImmutableArray < CSharpDirective > directives ,
1079
+ Action < IDictionary < string , string > > ? addGlobalProperties )
1050
1080
{
1051
1081
var projectRoot = CreateProjectRootElement ( projectCollection ) ;
1052
1082
@@ -1069,7 +1099,7 @@ ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
1069
1099
var projectFileWriter = new StringWriter ( ) ;
1070
1100
WriteProjectFile (
1071
1101
projectFileWriter ,
1072
- Directives ,
1102
+ directives ,
1073
1103
isVirtualProject : true ,
1074
1104
targetFilePath : EntryPointFileFullPath ,
1075
1105
artifactsPath : ArtifactsPath ,
@@ -1589,6 +1619,28 @@ static bool Fill(ref WhiteSpaceInfo info, in SyntaxTriviaList triviaList, int in
1589
1619
}
1590
1620
}
1591
1621
1622
+ /// <summary>
1623
+ /// If there are any <c>#:project</c> <paramref name="directives"/>, expand <c>$()</c> in them and then resolve the project paths.
1624
+ /// </summary>
1625
+ public static ImmutableArray < CSharpDirective > EvaluateDirectives (
1626
+ ProjectInstance ? project ,
1627
+ ImmutableArray < CSharpDirective > directives ,
1628
+ SourceFile sourceFile ,
1629
+ DiagnosticBag diagnostics )
1630
+ {
1631
+ if ( directives . OfType < CSharpDirective . Project > ( ) . Any ( ) )
1632
+ {
1633
+ return directives
1634
+ . Select ( d => d is CSharpDirective . Project p
1635
+ ? ( project is null ? p : p . WithName ( project . ExpandString ( p . Name ) ) )
1636
+ . ResolveProjectPath ( sourceFile , diagnostics )
1637
+ : d )
1638
+ . ToImmutableArray ( ) ;
1639
+ }
1640
+
1641
+ return directives ;
1642
+ }
1643
+
1592
1644
public static SourceText ? RemoveDirectivesFromFile ( ImmutableArray < CSharpDirective > directives , SourceText text )
1593
1645
{
1594
1646
if ( directives . Length == 0 )
@@ -1867,8 +1919,26 @@ public sealed class Package(in ParseInfo info) : Named(info)
1867
1919
/// <summary>
1868
1920
/// <c>#:project</c> directive.
1869
1921
/// </summary>
1870
- public sealed class Project ( in ParseInfo info ) : Named ( info )
1922
+ public sealed class Project : Named
1871
1923
{
1924
+ [ SetsRequiredMembers ]
1925
+ public Project ( in ParseInfo info , string name ) : base ( info )
1926
+ {
1927
+ Name = name ;
1928
+ OriginalName = name ;
1929
+ UnresolvedName = name ;
1930
+ }
1931
+
1932
+ /// <summary>
1933
+ /// Preserved across <see cref="WithName"/> calls.
1934
+ /// </summary>
1935
+ public required string OriginalName { get ; init ; }
1936
+
1937
+ /// <summary>
1938
+ /// Preserved across <see cref="ResolveProjectPath"/> calls.
1939
+ /// </summary>
1940
+ public required string UnresolvedName { get ; init ; }
1941
+
1872
1942
public static new Project ? Parse ( in ParseContext context )
1873
1943
{
1874
1944
var directiveText = context . DirectiveText ;
@@ -1878,11 +1948,32 @@ public sealed class Project(in ParseInfo info) : Named(info)
1878
1948
return context . Diagnostics . AddError < Project ? > ( context . SourceFile , context . Info . Span , string . Format ( CliCommandStrings . MissingDirectiveName , directiveKind ) ) ;
1879
1949
}
1880
1950
1951
+ return new Project ( context . Info , directiveText ) ;
1952
+ }
1953
+
1954
+ public Project WithName ( string name , bool preserveUnresolvedName = false )
1955
+ {
1956
+ return name == Name
1957
+ ? this
1958
+ : new Project ( Info , name )
1959
+ {
1960
+ OriginalName = OriginalName ,
1961
+ UnresolvedName = preserveUnresolvedName ? UnresolvedName : name ,
1962
+ } ;
1963
+ }
1964
+
1965
+ /// <summary>
1966
+ /// If the directive points to a directory, returns a new directive pointing to the corresponding project file.
1967
+ /// </summary>
1968
+ public Project ResolveProjectPath ( SourceFile sourceFile , DiagnosticBag diagnostics )
1969
+ {
1970
+ var directiveText = Name ;
1971
+
1881
1972
try
1882
1973
{
1883
1974
// If the path is a directory like '../lib', transform it to a project file path like '../lib/lib.csproj'.
1884
1975
// Also normalize blackslashes to forward slashes to ensure the directive works on all platforms.
1885
- var sourceDirectory = Path . GetDirectoryName ( context . SourceFile . Path ) ?? "." ;
1976
+ var sourceDirectory = Path . GetDirectoryName ( sourceFile . Path ) ?? "." ;
1886
1977
var resolvedProjectPath = Path . Combine ( sourceDirectory , directiveText . Replace ( '\\ ' , '/' ) ) ;
1887
1978
if ( Directory . Exists ( resolvedProjectPath ) )
1888
1979
{
@@ -1900,18 +1991,10 @@ public sealed class Project(in ParseInfo info) : Named(info)
1900
1991
}
1901
1992
catch ( GracefulException e )
1902
1993
{
1903
- context . Diagnostics . AddError ( context . SourceFile , context . Info . Span , string . Format ( CliCommandStrings . InvalidProjectDirective , e . Message ) , e ) ;
1994
+ diagnostics . AddError ( sourceFile , Info . Span , string . Format ( CliCommandStrings . InvalidProjectDirective , e . Message ) , e ) ;
1904
1995
}
1905
1996
1906
- return new Project ( context . Info )
1907
- {
1908
- Name = directiveText ,
1909
- } ;
1910
- }
1911
-
1912
- public Project WithName ( string name )
1913
- {
1914
- return new Project ( Info ) { Name = name } ;
1997
+ return WithName ( directiveText , preserveUnresolvedName : true ) ;
1915
1998
}
1916
1999
1917
2000
public override string ToString ( ) => $ "#:project { Name } ";
0 commit comments