8000 Use symlinks on Windows instead of cmd file by agocke · Pull Request #197 · dn-vm/dnvm · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Use symlinks on Windows instead of cmd file #197

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 1 commit into from
Apr 6, 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
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageVersion Include="StaticCs.Collections" Version="1.0.1" />
<PackageVersion Include="StaticCs.Async" Version="0.1.2" />
<PackageVersion Include="StaticCs.Result" Version="0.1.0" />
<PackageVersion Include="zio" Version="0.16.2" />
<PackageVersion Include="zio" Version="0.20.0" />
<PackageVersion Include="GitVersion.MSBuild" Version="5.10.3" />
<PackageVersion Include="Semver" Version="2.2.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
Expand Down
2 changes: 1 addition & 1 deletion src/dnvm/DnvmEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public sealed partial class DnvmEnv : IDisposable
public static UPath ManifestPath => UPath.Root / ManifestFileName;
public static UPath EnvPath => UPath.Root / "env";
public static UPath DnvmExePath => UPath.Root / Utilities.DnvmExeName;
public static UPath SymlinkPath => UPath.Root / Utilities.DotnetSymlinkName;
public static UPath SymlinkPath => UPath.Root / Utilities.DotnetExeName;
public static UPath GetSdkPath(SdkDirName sdkDirName) => UPath.Root / sdkDirName.Name;
/// <summary>
/// Default DNVM_HOME is
Expand Down
76 changes: 43 additions & 33 deletions src/dnvm/InstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Semver;
using Serde;
Expand Down Expand Up @@ -212,9 +213,10 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
logger.Info("Download link: " + link);

logger.Log($"Downloading SDK {sdkVersion} for {ridString}");
var error = await InstallSdkToDir(env.HttpClient, link, env.HomeFs, sdkInstallPath, env.TempFs, logger);
var curMuxerVersion = manifest.MuxerVersion(sdkDir);
var error = await InstallSdkToDir(curMuxerVersion, release.Runtime.Version, env.HttpClient, link, env.HomeFs, sdkInstallPath, env.TempFs, logger);

CreateSymlinkIfMissing(env, sdkDir);
CreateSymlinkIfMissing(logger, env, sdkDir);

var result = JsonSerializer.Serialize(manifest);
logger.Info("Existing manifest: " + result);
Expand All @@ -240,6 +242,8 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
}

internal static async Task<InstallError?> InstallSdkToDir(
SemVersion? curMuxerVersion,
SemVersion runtimeVersion,
ScopedHttpClient httpClient,
string downloadUrl,
IFileSystem destFs,
Expand Down Expand Up @@ -268,7 +272,13 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
}

logger.Log($"Installing to {destPath}");
string? extractResult = await Utilities.ExtractArchiveToDir(archivePath, tempFs, destFs, destPath);
string? extractResult = await Utilities.ExtractSdkToDir(
curMuxerVersion,
runtimeVersion,
archivePath,
tempFs,
destFs,
destPath);
File.Delete(archivePath);
if (extractResult != null)
{
Expand All @@ -294,12 +304,11 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
return null;
}

internal static void CreateSymlinkIfMissing(DnvmEnv dnvmFs, SdkDirName sdkDirName)
internal static void CreateSymlinkIfMissing(Logger logger, DnvmEnv dnvmEnv, SdkDirName sdkDirName)
{
var symlinkPath = dnvmFs.HomeFs.ConvertPathToInternal(UPath.Root + Utilities.DotnetSymlinkName);
if (!File.Exists(symlinkPath))
if (!dnvmEnv.HomeFs.FileExists(DnvmEnv.SymlinkPath))
{
RetargetSymlink(dnvmFs, sdkDirName);
RetargetSymlink(logger, dnvmEnv, sdkDirName);
}
}

Expand All @@ -317,38 +326,39 @@ internal static string ConstructArchiveName(
/// Creates a symlink from the dotnet exe in the dnvm home directory to the dotnet exe in the
/// sdk install directory.
/// </summary>
/// <remarks>
/// Doesn't use a symlink on Windows because the dotnet muxer doesn't properly resolve through
/// symlinks.
/// </remarks>
internal static void RetargetSymlink(DnvmEnv dnvmFs, SdkDirName sdkDirName)
internal static void RetargetSymlink(Logger logger, DnvmEnv dnvmEnv, SdkDirName sdkDirName)
{
var dnvmHome = dnvmFs.HomeFs.ConvertPathToInternal(UPath.Root);
RetargetSymlink(dnvmHome, sdkDirName);
var dotnetExePath = DnvmEnv.GetSdkPath(sdkDirName)/Utilities.DotnetExeName;
var realDotnetPath = dnvmEnv.RealPath(dotnetExePath);
logger.Info($"Retargeting symlink in {dnvmEnv.RealPath(UPath.Root)} to {realDotnetPath}");
if (!dnvmEnv.HomeFs.FileExists(dotnetExePath))
{
logger.Info("SDK install not found, skipping symlink creation.");
return;
}

static void RetargetSymlink(string dnvmHome, SdkDirName sdkDirName)
var homeFs = dnvmEnv.HomeFs;
// Delete if it already exists
try
{
homeFs.DeleteFile(DnvmEnv.SymlinkPath);
}
catch { }

// Create a symlink. We assume that the user has enabled developer mode in Windows,
// which is required to create symlinks.
try
{
homeFs.CreateSymbolicLink(DnvmEnv.SymlinkPath, dotnetExePath);
}
catch (IOException)
{
var symlinkPath = Path.Combine(dnvmHome, Utilities.DotnetSymlinkName);
var sdkInstallDir = Path.Combine(dnvmHome, sdkDirName.Name);
// Delete if it already exists
try
{
File.Delete(symlinkPath);
}
catch { }
if (OperatingSystem.IsWindows())
{
// On Windows, we can't create a symlink, so create a .cmd file that calls the dotnet.exe
File.WriteAllText(symlinkPath, $"""
@echo off
"%~dp0{sdkDirName.Name}\{Utilities.DotnetExeName}" %*
""");
}
else
{
// On Unix, we can create a symlink
File.CreateSymbolicLink(symlinkPath, Path.Combine(sdkInstallDir, Utilities.DotnetExeName));
Console.WriteLine("Failed to create symlink. " +
"Please make sure you have developer mode enabled.");
}
throw;
}
}
}
20 changes: 20 additions & 0 deletions src/dnvm/ManifestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ public static EqArray<RegisteredChannel> TrackedChannels(this Manifest manifest)
return manifest.RegisteredChannels.Where(x => !x.Untracked).ToEq();
}

/// <summary>
/// Calculates the version of the installed muxer. This is
/// Max(<all installed _runtime_ versions>).
/// If no SDKs are installed, returns null.
/// </summary>
public static SemVersion? MuxerVersion(this Manifest manifest, SdkDirName dir)
{
var installedSdks = manifest
.InstalledSdks
.Where(s => s.SdkDirName == dir)
.ToList();
if (installedSdks.Count == 0)
{
return null;
}
return installedSdks
.Select(s => s.RuntimeVersion)
.Max(SemVersion.SortOrderComparer);
}

public static Manifest AddSdk(
this Manifest manifest,
SemVersion semVersion,
Expand Down
2 changes: 2 additions & 0 deletions src/dnvm/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public static class Program
{
public static readonly SemVersion SemVer = SemVersion.Parse(GitVersionInformation.SemVer, SemVersionStyles.Strict);

public static readonly SemVersion BackupMuxerVersion = new SemVersion(9, 0, 3);

public static async Task<int> Main(string[] args)
{
var console = AnsiConsole.Console;
Expand Down
Binary file added src/dnvm/Resources/dotnet.exe
Binary file not shown.
File renamed without changes.
11 changes: 10 additions & 1 deletion src/dnvm/RestoreCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,16 @@ public static async Task<Result<SemVersion, Error>> Run(DnvmEnv env, Logger logg

var downloadUrl = component.Files.Single(f => f.Rid == Utilities.CurrentRID.ToString() && f.Url.EndsWith(Utilities.ZipSuffix)).Url;

var error = await InstallCommand.InstallSdkToDir(env.HttpClient, downloadUrl, env.CwdFs, installDir, env.TempFs, logger);
var error = await InstallCommand.InstallSdkToDir(
curMuxerVersion: null, // we shouldn't have a muxer yet, and we can overwrite it if we do
component.Version,
env.HttpClient,
downloadUrl,
env.CwdFs,
installDir,
env.TempFs,
logger
);
if (error is not null)
{
return Error.IoError;
Expand Down
6 changes: 3 additions & 3 deletions src/dnvm/SelectCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ public static async ValueTask<Result<Manifest, Result>> RunWithManifest(DnvmEnv
return Result.BadDirName;
}

return await SelectNewDir(env, newDir, manifest);
return await SelectNewDir(logger, env, newDir, manifest);
}

/// <summary>
/// Replaces the dotnet symlink with one pointing to the new SDK and
/// updates the manifest to reflect the new SDK dir.
/// </summary>
private static Task<Manifest> SelectNewDir(DnvmEnv env, SdkDirName newDir, Manifest manifest)
private static Task<Manifest> SelectNewDir(Logger logger, DnvmEnv env, SdkDirName newDir, Manifest manifest)
{
InstallCommand.RetargetSymlink(env, newDir);
InstallCommand.RetargetSymlink(logger, env, newDir);
return Task.FromResult(manifest with { CurrentSdkDir = newDir });
}
}
23 changes: 12 additions & 11 deletions src/dnvm/SelfInstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ public SelfInstallCommand(DnvmEnv env, Logger logger, CommandArguments.SelfInsta
_env = env;
_logger = logger;
_installArgs = args;
if (_installArgs.Verbose)
{
_logger.LogLevel = LogLevel.Info;
}
_feedUrls = _installArgs.FeedUrl is null
? _env.DotnetFeedUrls
: new[] { _installArgs.FeedUrl.TrimEnd('/') };
}

public static async Task<Result> Run(Logger logger, CommandArguments.SelfInstallArguments args)
{
if (args.Verbose)
{
logger.LogLevel = LogLevel.Info;
}

if (!Utilities.IsSingleFile)
{
logger.Log("Cannot self-install into target location: the current executable is not deployed as a single file.");
Expand Down Expand Up @@ -168,7 +169,7 @@ public async Task<Result> Run(UPath targetPath, Channel channel, SdkDirName sdkD
return Result.InstallFailed;
}

InstallCommand.RetargetSymlink(_env, sdkDirName);
InstallCommand.RetargetSymlink(_logger, _env, sdkDirName);

// Set up path
if (updateUserEnv)
Expand Down Expand Up @@ -196,7 +197,8 @@ internal static async Task<Result> SelfUpdate(string? destPath, Logger logger, D
}

var dnvmHome = dnvmEnv.HomeFs.ConvertPathToInternal(UPath.Root);
var SdkInstallPath = Path.Combine(dnvmHome, sdkDirName.Name);
logger.Info($"Installing to {dnvmHome}");
var sdkInstallPath = Path.Combine(dnvmHome, sdkDirName.Name);
if (destPath is null)
{
destPath = dnvmEnv.RealPath(DnvmEnv.DnvmExePath);
Expand All @@ -205,16 +207,15 @@ internal static async Task<Result> SelfUpdate(string? destPath, Logger logger, D
{
return Result.SelfInstallFailed;
}
logger.Info($"Retargeting symlink in {dnvmHome} to {SdkInstallPath}");
InstallCommand.RetargetSymlink(dnvmEnv, sdkDirName);
InstallCommand.RetargetSymlink(logger, dnvmEnv, sdkDirName);
if (!OperatingSystem.IsWindows())
{
await WriteEnvFile(dnvmEnv, SdkInstallPath, logger);
await WriteEnvFile(dnvmEnv, sdkInstallPath, logger);
}
else
{
// Remove default SDK install path from PATH if present
RemoveFromPath(dnvmEnv, SdkInstallPath);
RemoveFromPath(dnvmEnv, sdkInstallPath);
}

return Result.Success;
Expand Down Expand Up @@ -260,7 +261,7 @@ private static Task WriteEnvFile(DnvmEnv dnvmFs, string sdkInstallDir, Logger lo
private static string GetEnvShContent()
{
var asm = Assembly.GetExecutingAssembly();
using var stream = asm.GetManifestResourceStream("dnvm.env.sh")!;
using var stream = asm.GetManifestResourceStream("dnvm.Resources.env.sh")!;
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
Expand Down
11 changes: 8 additions & 3 deletions src/dnvm/UpdateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ async Task HandleDownload(string tempDownloadPath)
{
return SelfUpdateFailed;
}
var exitCode = RunSelfInstall(dnvmTmpPath, Utilities.ProcessPath);
var exitCode = RunSelfInstall(_logger, dnvmTmpPath, Utilities.ProcessPath);
return exitCode == 0 ? Success : SelfUpdateFailed;
}

Expand Down Expand Up @@ -375,13 +375,18 @@ public static async Task<bool> ValidateBinary(Logger? logger, string fileName)
return false;
}

public static int RunSelfInstall(string newFileName, string oldPath)
public static int RunSelfInstall(Logger logger, string newFileName, string oldPath)
{
var psi = new ProcessStartInfo
{
FileName = newFileName,
ArgumentList = { "selfinstall", "--update" }
ArgumentList = { "selfinstall", "--update", "--dest-path", $"{oldPath}" }
};
if (logger.LogLevel >= LogLevel.Info)
{
psi.ArgumentList.Add("--verbose");
}
logger.Log("Running selfinstall: " + string.Join(" ", psi.ArgumentList));
var proc = Process.Start(psi);
proc!.WaitForExit();
return proc.ExitCode;
Expand Down
Loading
0