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 ,
@@ -1604,6 +1634,28 @@ static bool Fill(ref WhiteSpaceInfo info, in SyntaxTriviaList triviaList, int in
16041634 }
16051635 }
16061636
1637+ /// <summary>
1638+ /// If there are any <c>#:project</c> <paramref name="directives"/>, expand <c>$()</c> in them and then resolve the project paths.
1639+ /// </summary>
1640+ public static ImmutableArray < CSharpDirective > EvaluateDirectives (
1641+ ProjectInstance ? project ,
1642+ ImmutableArray < CSharpDirective > directives ,
1643+ SourceFile sourceFile ,
1644+ DiagnosticBag diagnostics )
1645+ {
1646+ if ( directives . OfType < CSharpDirective . Project > ( ) . Any ( ) )
1647+ {
1648+ return directives
1649+ . Select ( d => d is CSharpDirective . Project p
1650+ ? ( project is null ? p : p . WithName ( project . ExpandString ( p . Name ) ) )
1651+ . ResolveProjectPath ( sourceFile , diagnostics )
1652+ : d )
1653+ . ToImmutableArray ( ) ;
1654+ }
1655+
1656+ return directives ;
1657+ }
1658+
16071659 public static SourceText ? RemoveDirectivesFromFile ( ImmutableArray < CSharpDirective > directives , SourceText text )
16081660 {
16091661 if ( directives . Length == 0 )
@@ -1882,8 +1934,26 @@ public sealed class Package(in ParseInfo info) : Named(info)
18821934 /// <summary>
18831935 /// <c>#:project</c> directive.
18841936 /// </summary>
1885- public sealed class Project ( in ParseInfo info ) : Named ( info )
1937+ public sealed class Project : Named
18861938 {
1939+ [ SetsRequiredMembers ]
1940+ public Project ( in ParseInfo info , string name ) : base ( info )
1941+ {
1942+ Name = name ;
1943+ OriginalName = name ;
1944+ UnresolvedName = name ;
1945+ }
1946+
1947+ /// <summary>
1948+ /// Preserved across <see cref="WithName"/> calls.
1949+ /// </summary>
1950+ public required string OriginalName { get ; init ; }
1951+
1952+ /// <summary>
1953+ /// Preserved across <see cref="ResolveProjectPath"/> calls.
1954+ /// </summary>
1955+ public required string UnresolvedName { get ; init ; }
1956+
18871957 public static new Project ? Parse ( in ParseContext context )
18881958 {
18891959 var directiveText = context . DirectiveText ;
@@ -1893,11 +1963,32 @@ public sealed class Project(in ParseInfo info) : Named(info)
18931963 return context . Diagnostics . AddError < Project ? > ( context . SourceFile , context . Info . Span , string . Format ( CliCommandStrings . MissingDirectiveName , directiveKind ) ) ;
18941964 }
18951965
1966+ return new Project ( context . Info , directiveText ) ;
1967+ }
1968+
1969+ public Project WithName ( string name , bool preserveUnresolvedName = false )
1970+ {
1971+ return name == Name
1972+ ? this
1973+ : new Project ( Info , name )
1974+ {
1975+ OriginalName = OriginalName ,
1976+ UnresolvedName = preserveUnresolvedName ? UnresolvedName : name ,
1977+ } ;
1978+ }
1979+
1980+ /// <summary>
1981+ /// If the directive points to a directory, returns a new directive pointing to the corresponding project file.
1982+ /// </summary>
1983+ public Project ResolveProjectPath ( SourceFile sourceFile , DiagnosticBag diagnostics )
1984+ {
1985+ var directiveText = Name ;
1986+
18961987 try
18971988 {
18981989 // If the path is a directory like '../lib', transform it to a project file path like '../lib/lib.csproj'.
18991990 // Also normalize blackslashes to forward slashes to ensure the directive works on all platforms.
1900- var sourceDirectory = Path . GetDirectoryName ( context . SourceFile . Path ) ?? "." ;
1991+ var sourceDirectory = Path . GetDirectoryName ( sourceFile . Path ) ?? "." ;
19011992 var resolvedProjectPath = Path . Combine ( sourceDirectory , directiveText . Replace ( '\\ ' , '/' ) ) ;
19021993 if ( Directory . Exists ( resolvedProjectPath ) )
19031994 {
@@ -1915,18 +2006,10 @@ public sealed class Project(in ParseInfo info) : Named(info)
19152006 }
19162007 catch ( GracefulException e )
19172008 {
1918- context . Diagnostics . AddError ( context . SourceFile , context . Info . Span , string . Format ( CliCommandStrings . InvalidProjectDirective , e . Message ) , e ) ;
2009+ diagnostics . AddError ( sourceFile , Info . Span , string . Format ( CliCommandStrings . InvalidProjectDirective , e . Message ) , e ) ;
19192010 }
19202011
1921- return new Project ( context . Info )
1922- {
1923- Name = directiveText ,
1924- } ;
1925- }
1926-
1927- public Project WithName ( string name )
1928- {
1929- return new Project ( Info ) { Name = name } ;
2012+ return WithName ( directiveText , preserveUnresolvedName : true ) ;
19302013 }
19312014
19322015 public override string ToString ( ) => $ "#:project { Name } ";
0 commit comments