@@ -1032,6 +1032,62 @@ public void DirectoryBuildProps()
10321032 . And . HaveStdOut ( "Hello from TestName" ) ;
10331033 }
10341034
1035+ /// <summary>
1036+ /// Implicit build files are taken from the folder of the symbolic link itself, not its target.
1037+ /// This is equivalent to the behavior of symlinked project files.
1038+ /// See <see href="https://github.com/dotnet/sdk/pull/52064#issuecomment-3628958688"/>.
1039+ /// </summary>
1040+ [ Fact ]
1041+ public void DirectoryBuildProps_SymbolicLink ( )
1042+ {
1043+ var testInstance = _testAssetsManager . CreateTestDirectory ( ) ;
1044+
1045+ var dir1 = Path . Join ( testInstance . Path , "dir1" ) ;
1046+ Directory . CreateDirectory ( dir1 ) ;
1047+
1048+ var originalPath = Path . Join ( dir1 , "original.cs" ) ;
1049+ File . WriteAllText ( originalPath , s_program ) ;
1050+
1051+ File . WriteAllText ( Path . Join ( dir1 , "Directory.Build.props" ) , """
1052+ <Project>
1053+ <PropertyGroup>
1054+ <AssemblyName>OriginalAssemblyName</AssemblyName>
1055+ </PropertyGroup>
1056+ </Project>
1057+ """ ) ;
1058+
1059+ var dir2 = Path . Join ( testInstance . Path , "dir2" ) ;
1060+ Directory . CreateDirectory ( dir2 ) ;
1061+
1062+ var programFileName = "linked.cs" ;
1063+ var programPath = Path . Join ( dir2 , programFileName ) ;
1064+
1065+ File . CreateSymbolicLink ( path : programPath , pathToTarget : originalPath ) ;
1066+
1067+ File . WriteAllText ( Path . Join ( dir2 , "Directory.Build.props" ) , """
1068+ <Project>
1069+ <PropertyGroup>
1070+ <AssemblyName>LinkedAssemblyName</AssemblyName>
1071+ </PropertyGroup>
1072+ </Project>
1073+ """ ) ;
1074+
1075+ new DotnetCommand ( Log , "run" , programFileName )
1076+ . WithWorkingDirectory ( dir2 )
1077+ . Execute ( )
1078+ . Should ( ) . Pass ( )
1079+ . And . HaveStdOut ( "Hello from LinkedAssemblyName" ) ;
1080+
1081+ // Removing the Directory.Build.props should be detected by up-to-date check.
1082+ File . Delete ( Path . Join ( dir2 , "Directory.Build.props" ) ) ;
1083+
1084+ new DotnetCommand ( Log , "run" , programFileName )
1085+ . WithWorkingDirectory ( dir2 )
1086+ . Execute ( )
1087+ . Should ( ) . Pass ( )
1088+ . And . HaveStdOut ( "Hello from linked" ) ;
1089+ }
1090+
10351091 /// <summary>
10361092 /// Overriding default (implicit) properties of file-based apps via implicit build files.
10371093 /// </summary>
@@ -3414,6 +3470,82 @@ public void UpToDate_InvalidOptions()
34143470 . And . HaveStdErrContaining ( string . Format ( CliCommandStrings . CannotCombineOptions , RunCommandDefinition . NoCacheOption . Name , RunCommandDefinition . NoBuildOption . Name ) ) ;
34153471 }
34163472
3473+ /// <summary>
3474+ /// <see cref="UpToDate"/> optimization should see through symlinks.
3475+ /// See <see href="https://github.com/dotnet/sdk/issues/52063"/>.
3476+ /// </summary>
3477+ [ Fact ]
3478+ public void UpToDate_SymbolicLink ( )
3479+ {
3480+ var testInstance = _testAssetsManager . CreateTestDirectory ( ) ;
3481+
3482+ var originalPath = Path . Join ( testInstance . Path , "original.cs" ) ;
3483+ var code = """
3484+ #!/usr/bin/env dotnet
3485+ Console.WriteLine("v1");
3486+ """ ;
3487+ var utf8NoBom = new UTF8Encoding ( encoderShouldEmitUTF8Identifier : false ) ;
3488+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
3489+
3490+ var programFileName = "linked" ;
3491+ var programPath = Path . Join ( testInstance . Path , programFileName ) ;
3492+
3493+ File . CreateSymbolicLink ( path : programPath , pathToTarget : originalPath ) ;
3494+
3495+ // Remove artifacts from possible previous runs of this test.
3496+ var artifactsDir = VirtualProjectBuildingCommand . GetArtifactsPath ( programPath ) ;
3497+ if ( Directory . Exists ( artifactsDir ) ) Directory . Delete ( artifactsDir , recursive : true ) ;
3498+
3499+ Build ( testInstance , BuildLevel . All , expectedOutput : "v1" , programFileName : programFileName ) ;
3500+
3501+ Build ( testInstance , BuildLevel . None , expectedOutput : "v1" , programFileName : programFileName ) ;
3502+
3503+ code = code . Replace ( "v1" , "v2" ) ;
3504+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
3505+
3506+ Build ( testInstance , BuildLevel . Csc , expectedOutput : "v2" , programFileName : programFileName ) ;
3507+ }
3508+
3509+ /// <summary>
3510+ /// Similar to <see cref="UpToDate_SymbolicLink"/> but with a chain of symlinks.
3511+ /// </summary>
3512+ [ Fact ]
3513+ public void UpToDate_SymbolicLink2 ( )
3514+ {
3515+ var testInstance = _testAssetsManager . CreateTestDirectory ( ) ;
3516+
3517+ var originalPath = Path . Join ( testInstance . Path , "original.cs" ) ;
3518+ var code = """
3519+ #!/usr/bin/env dotnet
3520+ Console.WriteLine("v1");
3521+ """ ;
3522+ var utf8NoBom = new UTF8Encoding ( encoderShouldEmitUTF8Identifier : false ) ;
3523+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
3524+
3525+ var intermediateFileName = "linked1" ;
3526+ var intermediatePath = Path . Join ( testInstance . Path , intermediateFileName ) ;
3527+
3528+ File . CreateSymbolicLink ( path : intermediatePath , pathToTarget : originalPath ) ;
3529+
3530+ var programFileName = "linked2" ;
3531+ var programPath = Path . Join ( testInstance . Path , programFileName ) ;
3532+
3533+ File . CreateSymbolicLink ( path : programPath , pathToTarget : intermediatePath ) ;
3534+
3535+ // Remove artifacts from possible previous runs of this test.
3536+ var artifactsDir = VirtualProjectBuildingCommand . GetArtifactsPath ( programPath ) ;
3537+ if ( Directory . Exists ( artifactsDir ) ) Directory . Delete ( artifactsDir , recursive : true ) ;
3538+
3539+ Build ( testInstance , BuildLevel . All , expectedOutput : "v1" , programFileName : programFileName ) ;
3540+
3541+ Build ( testInstance , BuildLevel . None , expectedOutput : "v1" , programFileName : programFileName ) ;
3542+
3543+ code = code . Replace ( "v1" , "v2" ) ;
3544+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
3545+
3546+ Build ( testInstance , BuildLevel . Csc , expectedOutput : "v2" , programFileName : programFileName ) ;
3547+ }
3548+
34173549 /// <summary>
34183550 /// Up-to-date checks and optimizations currently don't support other included files.
34193551 /// </summary>
@@ -3723,6 +3855,41 @@ Hello from Program
37233855 """ ) ;
37243856 }
37253857
3858+ /// <summary>
3859+ /// Combination of <see cref="UpToDate_SymbolicLink"/> and <see cref="CscOnly"/>.
3860+ /// </summary>
3861+ [ Fact ]
3862+ public void CscOnly_SymbolicLink ( )
3863+ {
3864+ var testInstance = _testAssetsManager . CreateTestDirectory ( baseDirectory : OutOfTreeBaseDirectory ) ;
3865+
3866+ var originalPath = Path . Join ( testInstance . Path , "original.cs" ) ;
3867+ var code = """
3868+ #!/usr/bin/env dotnet
3869+ Console.WriteLine("v1");
3870+ """ ;
3871+ var utf8NoBom = new UTF8Encoding ( encoderShouldEmitUTF8Identifier : false ) ;
3872+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
3873+
3874+ var programFileName = "linked" ;
3875+ var programPath = Path . Join ( testInstance . Path , programFileName ) ;
3876+
3877+ File . CreateSymbolicLink ( path : programPath , pathToTarget : originalPath ) ;
3878+
3879+ // Remove artifacts from possible previous runs of this test.
3880+ var artifactsDir = VirtualProjectBuildingCommand . GetArtifactsPath ( programPath ) ;
3881+ if ( Directory . Exists ( artifactsDir ) ) Directory . Delete ( artifactsDir , recursive : true ) ;
3882+
3883+ Build ( testInstance , BuildLevel . Csc , expectedOutput : "v1" , programFileName : programFileName ) ;
3884+
3885+ Build ( testInstance , BuildLevel . None , expectedOutput : "v1" , programFileName : programFileName ) ;
3886+
3887+ code = code . Replace ( "v1" , "v2" ) ;
3888+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
3889+
3890+ Build ( testInstance , BuildLevel . Csc , expectedOutput : "v2" , programFileName : programFileName ) ;
3891+ }
3892+
37263893 /// <summary>
37273894 /// Tests an optimization which remembers CSC args from prior MSBuild runs and can skip subsequent MSBuild invocations and call CSC directly.
37283895 /// This optimization kicks in when the file has some <c>#:</c> directives (then the simpler "hard-coded CSC args" optimization cannot be used).
@@ -3882,6 +4049,40 @@ public void CscOnly_AfterMSBuild_HardLinks()
38824049 Build ( testInstance , BuildLevel . Csc , expectedOutput : "Hi from Program" ) ;
38834050 }
38844051
4052+ /// <summary>
4053+ /// Combination of <see cref="UpToDate_SymbolicLink"/> and <see cref="CscOnly_AfterMSBuild"/>.
4054+ /// </summary>
4055+ [ Fact ]
4056+ public void CscOnly_AfterMSBuild_SymbolicLink ( )
4057+ {
4058+ var testInstance = _testAssetsManager . CreateTestDirectory ( baseDirectory : OutOfTreeBaseDirectory ) ;
4059+
4060+ var originalPath = Path . Join ( testInstance . Path , "original.cs" ) ;
4061+ var code = """
4062+ #!/usr/bin/env dotnet
4063+ #:property Configuration=Release
4064+ Console.WriteLine("v1");
4065+ """ ;
4066+ var utf8NoBom = new UTF8Encoding ( encoderShouldEmitUTF8Identifier : false ) ;
4067+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
4068+
4069+ var programFileName = "linked" ;
4070+ var programPath = Path . Join ( testInstance . Path , programFileName ) ;
4071+
4072+ File . CreateSymbolicLink ( path : programPath , pathToTarget : originalPath ) ;
4073+
4074+ // Remove artifacts from possible previous runs of this test.
4075+ var artifactsDir = VirtualProjectBuildingCommand . GetArtifactsPath ( programPath ) ;
4076+ if ( Directory . Exists ( artifactsDir ) ) Directory . Delete ( artifactsDir , recursive : true ) ;
4077+
4078+ Build ( testInstance , BuildLevel . All , expectedOutput : "v1" , programFileName : programFileName ) ;
4079+
4080+ code = code . Replace ( "v1" , "v2" ) ;
4081+ File . WriteAllText ( originalPath , code , utf8NoBom ) ;
4082+
4083+ Build ( testInstance , BuildLevel . Csc , expectedOutput : "v2" , programFileName : programFileName ) ;
4084+ }
4085+
38854086 /// <summary>
38864087 /// See <see cref="CscOnly_AfterMSBuild"/>.
38874088 /// This optimization currently does not support <c>#:project</c> references and hence is disabled if those are present.
@@ -4409,6 +4610,35 @@ public void EntryPointFilePath_WithUnicodeCharacters()
44094610 . And . HaveStdOut ( $ "EntryPointFilePath: { filePath } ") ;
44104611 }
44114612
4613+ [ Fact ]
4614+ public void EntryPointFilePath_SymbolicLink ( )
4615+ {
4616+ var testInstance = _testAssetsManager . CreateTestDirectory ( ) ;
4617+ var fileName = "Program.cs" ;
4618+ var programPath = Path . Join ( testInstance . Path , fileName ) ;
4619+ File . WriteAllText ( programPath , """
4620+ #!/usr/bin/env dotnet
4621+ var entryPointFilePath = AppContext.GetData("EntryPointFilePath") as string;
4622+ Console.WriteLine($"EntryPointFilePath: {entryPointFilePath}");
4623+ """ ) ;
4624+
4625+ new DotnetCommand ( Log , "run" , fileName )
4626+ . WithWorkingDirectory ( testInstance . Path )
4627+ . Execute ( )
4628+ . Should ( ) . Pass ( )
4629+ . And . HaveStdOut ( $ "EntryPointFilePath: { programPath } ") ;
4630+
4631+ var linkName = "linked" ;
4632+ var linkPath = Path . Join ( testInstance . Path , linkName ) ;
4633+ File . CreateSymbolicLink ( linkPath , programPath ) ;
4634+
4635+ new DotnetCommand ( Log , "run" , linkName )
4636+ . WithWorkingDirectory ( testInstance . Path )
4637+ . Execute ( )
4638+ . Should ( ) . Pass ( )
4639+ . And . HaveStdOut ( $ "EntryPointFilePath: { linkPath } ") ;
4640+ }
4641+
44124642 [ Fact ]
44134643 public void MSBuildGet_Simple ( )
44144644 {
0 commit comments