66using System . Collections . Immutable ;
77using System . Collections . ObjectModel ;
88using System . Diagnostics ;
9+ using System . Diagnostics . CodeAnalysis ;
910using System . Security ;
1011using System . Text . Json ;
1112using System . Text . Json . Serialization ;
@@ -164,14 +165,26 @@ public VirtualProjectBuildingCommand(
164165 /// </summary>
165166 public bool NoWriteBuildMarkers { get ; init ; }
166167
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+
167181 public ImmutableArray < CSharpDirective > Directives
168182 {
169183 get
170184 {
171185 if ( field . IsDefault )
172186 {
173- var sourceFile = SourceFile . Load ( EntryPointFileFullPath ) ;
174- field = FindDirectives ( sourceFile , reportAllErrors : false , DiagnosticBag . ThrowOnFirst ( ) ) ;
187+ field = FindDirectives ( EntryPointSourceFile , reportAllErrors : false , DiagnosticBag . ThrowOnFirst ( ) ) ;
175188 Debug . Assert ( ! field . IsDefault ) ;
176189 }
177190
@@ -1047,6 +1060,23 @@ public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection
10471060 private ProjectInstance CreateProjectInstance (
10481061 ProjectCollection projectCollection ,
10491062 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 )
10501080 {
10511081 var projectRoot = CreateProjectRootElement ( projectCollection ) ;
10521082
@@ -1069,7 +1099,7 @@ ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
10691099 var projectFileWriter = new StringWriter ( ) ;
10701100 WriteProjectFile (
10711101 projectFileWriter ,
1072- Directives ,
1102+ directives ,
10731103 isVirtualProject : true ,
10741104 targetFilePath : EntryPointFileFullPath ,
10751105 artifactsPath : ArtifactsPath ,
@@ -1589,6 +1619,28 @@ static bool Fill(ref WhiteSpaceInfo info, in SyntaxTriviaList triviaList, int in
15891619 }
15901620 }
15911621
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+
15921644 public static SourceText ? RemoveDirectivesFromFile ( ImmutableArray < CSharpDirective > directives , SourceText text )
15931645 {
15941646 if ( directives . Length == 0 )
@@ -1867,8 +1919,26 @@ public sealed class Package(in ParseInfo info) : Named(info)
18671919 /// <summary>
18681920 /// <c>#:project</c> directive.
18691921 /// </summary>
1870- public sealed class Project ( in ParseInfo info ) : Named ( info )
1922+ public sealed class Project : Named
18711923 {
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+
18721942 public static new Project ? Parse ( in ParseContext context )
18731943 {
18741944 var directiveText = context . DirectiveText ;
@@ -1878,11 +1948,32 @@ public sealed class Project(in ParseInfo info) : Named(info)
18781948 return context . Diagnostics . AddError < Project ? > ( context . SourceFile , context . Info . Span , string . Format ( CliCommandStrings . MissingDirectiveName , directiveKind ) ) ;
18791949 }
18801950
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+
18811972 try
18821973 {
18831974 // If the path is a directory like '../lib', transform it to a project file path like '../lib/lib.csproj'.
18841975 // 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 ) ?? "." ;
18861977 var resolvedProjectPath = Path . Combine ( sourceDirectory , directiveText . Replace ( '\\ ' , '/' ) ) ;
18871978 if ( Directory . Exists ( resolvedProjectPath ) )
18881979 {
@@ -1900,18 +1991,10 @@ public sealed class Project(in ParseInfo info) : Named(info)
19001991 }
19011992 catch ( GracefulException e )
19021993 {
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 ) ;
19041995 }
19051996
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 ) ;
19151998 }
19161999
19172000 public override string ToString ( ) => $ "#:project { Name } ";
0 commit comments