Skip to content

[release/9.0-staging] Revert change to follow symlinks of dotnet host #116244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 221 additions & 16 deletions src/installer/tests/HostActivation.Tests/SymbolicLinks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

using FluentAssertions;
using Microsoft.DotNet.Cli.Build.Framework;
Expand All @@ -21,6 +23,165 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture)
sharedTestState = fixture;
}

[Theory]
[InlineData("a/b/SymlinkToFrameworkDependentApp")]
[InlineData("a/SymlinkToFrameworkDependentApp")]
public void Symlink_all_files_fx(string symlinkRelativePath)
{
using var testDir = TestArtifact.Create("symlink");
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));

// Symlink every file in the app directory
var symlinks = new List<SymLink>();
try
{
foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location))
{
var fileName = Path.GetFileName(file);
var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName);
Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
symlinks.Add(new SymLink(symlinkPath, file));
}

var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))
.CaptureStdErr()
.CaptureStdOut()
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
.Execute();

// This should succeed on all platforms, but for different reasons:
// * Windows: The apphost will look next to the symlink for the app dll and find the symlinked dll
// * Unix: The apphost will look next to the resolved apphost for the app dll and find the real thing
result
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
finally
{
foreach (var symlink in symlinks)
{
symlink.Dispose();
}
}
}

[Theory]
[InlineData("a/b/SymlinkToFrameworkDependentApp")]
[InlineData("a/SymlinkToFrameworkDependentApp")]
public void Symlink_split_files_fx(string symlinkRelativePath)
{
using var testDir = TestArtifact.Create("symlink");

// Split the app into two directories, one for the apphost and one for the rest of the files
var appHostDir = Path.Combine(testDir.Location, "apphost");
var appFilesDir = Path.Combine(testDir.Location, "appfiles");
Directory.CreateDirectory(appHostDir);
Directory.CreateDirectory(appFilesDir);

var appHostName = Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe);

File.Copy(
sharedTestState.FrameworkDependentApp.AppExe,
Path.Combine(appHostDir, appHostName));

foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location))
{
var fileName = Path.GetFileName(file);
if (fileName != appHostName)
{
File.Copy(file, Path.Combine(appFilesDir, fileName));
}
}

// Symlink all of the above into a single directory
var targetPath = Path.Combine(testDir.Location, symlinkRelativePath);
Directory.CreateDirectory(targetPath);
var symlinks = new List<SymLink>();
try
{
foreach (var file in Directory.EnumerateFiles(appFilesDir))
{
var fileName = Path.GetFileName(file);
var symlinkPath = Path.Combine(targetPath, fileName);
Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
symlinks.Add(new SymLink(symlinkPath, file));
}
symlinks.Add(new SymLink(
Path.Combine(targetPath, appHostName),
Path.Combine(appHostDir, appHostName)));

var result = Command.Create(Path.Combine(targetPath, appHostName))
.CaptureStdErr()
.CaptureStdOut()
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
.Execute();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// On Windows, the apphost will look next to the symlink for the app dll and find the symlinks
result
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
else
{
// On Unix, the apphost will not find the app files next to the symlink
result
.Should().Fail()
.And.HaveStdErrContaining("The application to execute does not exist");
}
}
finally
{
foreach (var symlink in symlinks)
{
symlink.Dispose();
}
}
}

[Theory]
[InlineData("a/b/SymlinkToFrameworkDependentApp")]
[InlineData("a/SymlinkToFrameworkDependentApp")]
public void Symlink_all_files_self_contained(string symlinkRelativePath)
{
using var testDir = TestArtifact.Create("symlink");
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));

// Symlink every file in the app directory
var symlinks = new List<SymLink>();
try
{
foreach (var file in Directory.EnumerateFiles(sharedTestState.SelfContainedApp.Location))
{
var fileName = Path.GetFileName(file);
var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName);
Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
symlinks.Add(new SymLink(symlinkPath, file));
}

var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))
.CaptureStdErr()
.CaptureStdOut()
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
.Execute();

// This should succeed on all platforms, but for different reasons:
// * Windows: The apphost will look next to the symlink for the files and find the symlinks
// * Unix: The apphost will look next to the resolved apphost for the files and find the real thing
result
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
finally
{
foreach (var symlink in symlinks)
{
symlink.Dispose();
}
}
}

[Theory]
[InlineData ("a/b/SymlinkToApphost")]
[InlineData ("a/SymlinkToApphost")]
Expand All @@ -33,12 +194,23 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath)
var symlinkFullPath = Path.Combine(testDir.Location, symlinkRelativePath);

using var symlink = new SymLink(symlinkFullPath, sharedTestState.SelfContainedApp.AppExe);
Command.Create(symlinkFullPath)
var result = Command.Create(symlinkFullPath)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
.Execute();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
result
.Should().Fail()
.And.HaveStdErrContaining("The application to execute does not exist");
}
else
{
result
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
}
}

Expand All @@ -63,12 +235,23 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa
Directory.CreateDirectory(Path.GetDirectoryName(symlink1Path));
using var symlink1 = new SymLink(symlink1Path, symlink2Path);

Command.Create(symlink1.SrcPath)
var result = Command.Create(symlink1.SrcPath)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
.Execute();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
result
.Should().Fail()
.And.HaveStdErrContaining("The application to execute does not exist");
}
else
{
result
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
}
}

Expand All @@ -86,13 +269,24 @@ public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePat
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));

using var symlink = new SymLink(Path.Combine(testDir.Location, symlinkRelativePath), sharedTestState.FrameworkDependentApp.AppExe);
Command.Create(symlink.SrcPath)
var result = Command.Create(symlink.SrcPath)
.CaptureStdErr()
.CaptureStdOut()
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
.Execute();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
result
.Should().Fail()
.And.HaveStdErrContaining("The application to execute does not exist");
}
else
{
result
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
}
}

Expand Down Expand Up @@ -144,12 +338,23 @@ public void Put_dotnet_behind_symlink()
var dotnetSymlink = Path.Combine(testDir.Location, Binaries.DotNet.FileName);

using var symlink = new SymLink(dotnetSymlink, TestContext.BuiltDotNet.DotnetExecutablePath);
Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll)
var result = Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
.Execute();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
result
.Should().Fail()
.And.HaveStdErrContaining($"[{Path.Combine(testDir.Location, "host", "fxr")}] does not exist");
}
else
{
result
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/native/corehost/corehost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ int exe_start(const int argc, const pal::char_t* argv[])
// Use realpath to find the path of the host, resolving any symlinks.
// hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host.
pal::string_t host_path;
if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path))
if (!pal::get_own_executable_path(&host_path) || !pal::fullpath(&host_path))
{
trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str());
return StatusCode::CoreHostCurHostFindFailure;
Expand Down
2 changes: 1 addition & 1 deletion src/native/corehost/fxr_resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ bool fxr_resolver::try_get_path(
bool search_global = (search & search_location_global) != 0;
pal::string_t default_install_location;
pal::string_t dotnet_root_env_var_name;
if (search_app_relative && pal::realpath(app_relative_dotnet_root))
if (search_app_relative && pal::fullpath(app_relative_dotnet_root))
{
trace::info(_X("Using app-relative location [%s] as runtime location."), app_relative_dotnet_root->c_str());
out_dotnet_root->assign(*app_relative_dotnet_root);
Expand Down
Loading