From 1c9317b851571d2b53df93250e1b4491a84152af Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sun, 17 Oct 2021 14:04:45 -0500 Subject: [PATCH] add in support for more dotnet environment variables (#117) --- .config/dotnet-tools.json | 2 +- .paket/Paket.Restore.targets | 19 ++-- CHANGELOG.md | 8 ++ src/Ionide.ProjInfo.Tool/Program.fs | 4 +- src/Ionide.ProjInfo/Library.fs | 136 ++++++++++++++++++-------- src/Ionide.ProjInfo/Utils.fs | 53 +++++++--- test/Ionide.ProjInfo.Tests/Program.fs | 11 +-- test/Ionide.ProjInfo.Tests/Tests.fs | 10 +- 8 files changed, 165 insertions(+), 78 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 225d4ad8..070fa394 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "paket": { - "version": "6.0.0-rc006", + "version": "6.2.1", "commands": [ "paket" ] diff --git a/.paket/Paket.Restore.targets b/.paket/Paket.Restore.targets index 03928aab..e230bb21 100644 --- a/.paket/Paket.Restore.targets +++ b/.paket/Paket.Restore.targets @@ -236,17 +236,16 @@ $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) %(PaketReferencesFileLinesInfo.PackageVersion) All - runtime - runtime - $(ExcludeAssets);contentFiles - $(ExcludeAssets);build;buildMultitargeting;buildTransitive + runtime + $(ExcludeAssets);contentFiles + $(ExcludeAssets);build;buildMultitargeting;buildTransitive true true @@ -366,9 +365,9 @@ PackageLicenseFile="$(PackageLicenseFile)" PackageLicenseExpression="$(PackageLicenseExpression)" PackageLicenseExpressionVersion="$(PackageLicenseExpressionVersion)" - PackageReadmeFile="$(PackageReadmeFile)" - NoDefaultExcludes="$(NoDefaultExcludes) "/> - + Readme="$(PackageReadmeFile)" + NoDefaultExcludes="$(NoDefaultExcludes)"/> + -> IWorkspaceLoader let parseProject (loaderFunc: LoaderFunc) (path: string) = let cwd = System.IO.Path.GetDirectoryName path |> System.IO.DirectoryInfo - let toolsPath = Ionide.ProjInfo.Init.init cwd + let toolsPath = Ionide.ProjInfo.Init.init cwd None let loader = loaderFunc (toolsPath, []) loader.LoadProjects([ path ], [], BinaryLogGeneration.Within cwd) let parseSolution (loaderFunc: LoaderFunc) (path: string) = let cwd = System.IO.Path.GetDirectoryName path |> System.IO.DirectoryInfo - let toolsPath = Ionide.ProjInfo.Init.init cwd + let toolsPath = Ionide.ProjInfo.Init.init cwd None let loader = loaderFunc (toolsPath, []) loader.LoadSln(path, [], BinaryLogGeneration.Within cwd) diff --git a/src/Ionide.ProjInfo/Library.fs b/src/Ionide.ProjInfo/Library.fs index 7f269f5c..0f3047ea 100644 --- a/src/Ionide.ProjInfo/Library.fs +++ b/src/Ionide.ProjInfo/Library.fs @@ -17,46 +17,95 @@ module SdkDiscovery = let internal msbuildForSdk (sdkPath: DirectoryInfo) = Path.Combine(sdkPath.FullName, "MSBuild.dll") - let private versionedPaths (root: string) = - System.IO.Directory.EnumerateDirectories root - |> Seq.choose - (fun dir -> - let dirName = Path.GetFileName dir + type DotnetRuntimeInfo = + { RuntimeName: string + Version: SemanticVersioning.Version + Path: DirectoryInfo } + + let private execDotnet (cwd: DirectoryInfo) (binaryFullPath: FileInfo) args = + let info = ProcessStartInfo() + info.WorkingDirectory <- cwd.FullName + info.FileName <- binaryFullPath.FullName - match SemanticVersioning.Version.TryParse dirName with - | true, v -> Some v - | false, _ -> None) - |> Seq.sortDescending - |> Seq.map (fun v -> v, Path.Combine(root, string v) |> DirectoryInfo) + for arg in args do + info.ArgumentList.Add arg - /// Given the DOTNET_ROOT, that is the directory where the `dotnet` binary is present and the sdk/runtimes/etc are, - /// enumerates the available SDKs in descending version order - let sdks (dotnetDirectory: DirectoryInfo) = - let sdksPath = Path.Combine(dotnetDirectory.FullName, "sdk") - versionedPaths sdksPath + info.RedirectStandardOutput <- true + let p = System.Diagnostics.Process.Start(info) + p.WaitForExit() + + seq { + while not p.StandardOutput.EndOfStream do + yield p.StandardOutput.ReadLine() + } + + let private (|SemVer|_|) version = + match SemanticVersioning.Version.TryParse version with + | true, v -> Some v + | false, _ -> None + + let private (|SdkOutputDirectory|) (path: string) = + path.TrimStart('[').TrimEnd(']') |> DirectoryInfo + + let private (|RuntimeParts|_|) (line: string) = + match line.IndexOf ' ' with + | -1 -> None + | n -> + let runtimeName, rest = line.[0..n - 1], line.[n + 1..] + + match rest.IndexOf ' ' with + | -1 -> None + | n -> Some(runtimeName, rest.[0..n - 1], rest.[n + 1..]) + + let private (|SdkParts|_|) (line: string) = + match line.IndexOf ' ' with + | -1 -> None + | n -> Some(line.[0..n - 1], line.[n + 1..]) /// Given the DOTNET_ROOT, that is the directory where the `dotnet` binary is present and the sdk/runtimes/etc are, /// enumerates the available runtimes in descending version order - let runtimes (dotnetDirectory: DirectoryInfo) = - let netcoreAppPath = Path.Combine(dotnetDirectory.FullName, "shared", "Microsoft.NETCore.App") - versionedPaths netcoreAppPath + let runtimes (dotnetBinaryPath: FileInfo) : DotnetRuntimeInfo [] = + execDotnet dotnetBinaryPath.Directory dotnetBinaryPath [ "--list-runtimes" ] + |> Seq.choose + (fun line -> + match line with + | RuntimeParts (runtimeName, SemVer version, SdkOutputDirectory path) -> + Some + { RuntimeName = runtimeName + Version = version + Path = Path.Combine(path.FullName, string version) |> DirectoryInfo } + | line -> None) + |> Seq.toArray + + type DotnetSdkInfo = + { Version: SemanticVersioning.Version + Path: DirectoryInfo } + + /// Given the DOTNET_sROOT, that is the directory where the `dotnet` binary is present and the sdk/runtimes/etc are, + /// enumerates the available SDKs in descending version order + let sdks (dotnetBinaryPath: FileInfo) : DotnetSdkInfo [] = + execDotnet dotnetBinaryPath.Directory dotnetBinaryPath [ "--list-sdks" ] + |> Seq.choose + (fun line -> + match line with + | SdkParts (SemVer sdkVersion, SdkOutputDirectory path) -> + Some + { Version = sdkVersion + Path = Path.Combine(path.FullName, string sdkVersion) |> DirectoryInfo } + | line -> None) + |> Seq.toArray /// performs a `dotnet --version` command at the given directory to get the version of the /// SDK active at that location. - let versionAt (cwd: DirectoryInfo) = - let exe = Paths.dotnetRoot - let info = ProcessStartInfo() - info.WorkingDirectory <- cwd.FullName - info.FileName <- exe - info.ArgumentList.Add("--version") - info.RedirectStandardOutput <- true - let p = System.Diagnostics.Process.Start(info) - p.WaitForExit() - let stdout = p.StandardOutput.ReadToEnd() + let versionAt (cwd: DirectoryInfo) (dotnetBinaryPath: FileInfo) = + execDotnet cwd dotnetBinaryPath [ "--version" ] + |> Seq.head + |> function + | version -> + match SemanticVersioning.Version.TryParse version with + | true, v -> Ok v + | false, _ -> Error(dotnetBinaryPath, [ "--version" ], cwd, version) - match SemanticVersioning.Version.TryParse stdout with - | true, v -> Ok v - | false, _ -> Error(exe, info.ArgumentList, cwd, stdout) [] module Init = @@ -113,7 +162,7 @@ module Init = match System.Environment.GetEnvironmentVariable "DOTNET_HOST_PATH" with | null - | "" -> Environment.SetEnvironmentVariable("DOTNET_HOST_PATH", Paths.dotnetRoot) + | "" -> Environment.SetEnvironmentVariable("DOTNET_HOST_PATH", Paths.dotnetRoot.FullName) | alreadySet -> () if resolveHandler <> null then @@ -124,17 +173,20 @@ module Init = /// Initialize the MsBuild integration. Returns path to MsBuild tool that was detected by Locator. Needs to be called before doing anything else. /// Call it again when the working directory changes. - let init (workingDirectory: DirectoryInfo) = - match SdkDiscovery.versionAt workingDirectory with + let init (workingDirectory: DirectoryInfo) (dotnetExe: FileInfo option) = + let exe = dotnetExe |> Option.defaultWith (fun _ -> Paths.dotnetRoot) + + match SdkDiscovery.versionAt workingDirectory exe with | Ok dotnetSdkVersionAtPath -> - let sdkVersion, sdkPath = - SdkDiscovery.sdks (Path.GetDirectoryName Paths.dotnetRoot |> DirectoryInfo) - |> Seq.skipWhile (fun (v, path) -> v > dotnetSdkVersionAtPath) - |> Seq.head - - let msbuild = SdkDiscovery.msbuildForSdk sdkPath - setupForSdkVersion sdkPath - ToolsPath msbuild + let sdks = SdkDiscovery.sdks exe + let sdkInfo: SdkDiscovery.DotnetSdkInfo option = sdks |> Array.skipWhile (fun { Version = v } -> v < dotnetSdkVersionAtPath) |> Array.tryHead + + match sdkInfo with + | Some sdkInfo -> + let msbuild = SdkDiscovery.msbuildForSdk sdkInfo.Path + setupForSdkVersion sdkInfo.Path + ToolsPath msbuild + | None -> failwithf $"Unable to get sdk versions at least from the string '{dotnetSdkVersionAtPath}'. This found sdks were {sdks |> Array.toList}" | Error (dotnetExe, args, cwd, erroringVersionString) -> failwithf $"Unable to parse sdk version from the string '{erroringVersionString}'. This value came from running `{dotnetExe} {args}` at path {cwd}" [] diff --git a/src/Ionide.ProjInfo/Utils.fs b/src/Ionide.ProjInfo/Utils.fs index 510f2a42..0823593f 100644 --- a/src/Ionide.ProjInfo/Utils.fs +++ b/src/Ionide.ProjInfo/Utils.fs @@ -1,19 +1,48 @@ namespace Ionide.ProjInfo +open System.Runtime.InteropServices +open System.IO +open System + module Paths = - /// provides the path to the `dotnet` binary running this library, duplicated from - /// https://github.com/dotnet/sdk/blob/b91b88aec2684e3d2988df8d838d3aa3c6240a35/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs#L39 - let dotnetRoot = - match System.Environment.GetEnvironmentVariable "DOTNET_HOST_PATH" with + let private isUnix = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + + let private dotnetBinaryName = + if isUnix then + "dotnet" + else + "dotnet.exe" + + let private potentialDotnetHostEnvVars = + [ "DOTNET_HOST_PATH", id // is a full path to dotnet binary + "DOTNET_ROOT", (fun s -> Path.Combine(s, dotnetBinaryName)) // needs dotnet binary appended + "DOTNET_ROOT(x86)", (fun s -> Path.Combine(s, dotnetBinaryName)) ] // needs dotnet binary appended + + let private existingEnvVarValue envVarValue = + match envVarValue with | null - | "" -> - System - .Diagnostics - .Process - .GetCurrentProcess() - .MainModule - .FileName - | alreadySet -> alreadySet + | "" -> None + | other -> Some other + + /// + /// provides the path to the `dotnet` binary running this library, respecting various dotnet environment variables + /// + let dotnetRoot = + potentialDotnetHostEnvVars + |> List.tryPick + (fun (envVar, transformer) -> + match Environment.GetEnvironmentVariable envVar |> existingEnvVarValue with + | Some varValue -> Some(transformer varValue |> FileInfo) + | None -> None) + |> Option.defaultWith + (fun _ -> + System + .Diagnostics + .Process + .GetCurrentProcess() + .MainModule + .FileName + |> FileInfo) let sdksPath (dotnetRoot: string) = System.IO.Path.Combine(dotnetRoot, "Sdks") diff --git a/test/Ionide.ProjInfo.Tests/Program.fs b/test/Ionide.ProjInfo.Tests/Program.fs index 795be393..2f63b4c2 100644 --- a/test/Ionide.ProjInfo.Tests/Program.fs +++ b/test/Ionide.ProjInfo.Tests/Program.fs @@ -11,16 +11,7 @@ open Expecto.Logging [] let main argv = - let baseDir = System.Environment.GetEnvironmentVariable "DOTNET_ROOT" - // need to set this because these tests aren't run directly via the `dotnet` binary - let dotnetExe = - if Environment.isMacOS || Environment.isUnix then - "dotnet" - else - "dotnet.exe" - - Environment.SetEnvironmentVariable("DOTNET_HOST_PATH", IO.Path.Combine(baseDir, dotnetExe)) - let toolsPath = Init.init (IO.DirectoryInfo Environment.CurrentDirectory) + let toolsPath = Init.init (IO.DirectoryInfo Environment.CurrentDirectory) None Tests.runTestsWithArgs { defaultConfig with diff --git a/test/Ionide.ProjInfo.Tests/Tests.fs b/test/Ionide.ProjInfo.Tests/Tests.fs index 386e6551..2afc579a 100644 --- a/test/Ionide.ProjInfo.Tests/Tests.fs +++ b/test/Ionide.ProjInfo.Tests/Tests.fs @@ -1034,4 +1034,12 @@ let tests toolsPath = debugTets toolsPath "WorkspaceLoaderViaProjectGraph" WorkspaceLoaderViaProjectGraph.Create //Binlog test testSample2WithBinLog toolsPath "WorkspaceLoader" WorkspaceLoader.Create - testSample2WithBinLog toolsPath "WorkspaceLoaderViaProjectGraph" WorkspaceLoaderViaProjectGraph.Create ] + testSample2WithBinLog toolsPath "WorkspaceLoaderViaProjectGraph" WorkspaceLoaderViaProjectGraph.Create + test "can get runtimes" { + let runtimes = SdkDiscovery.runtimes Paths.dotnetRoot + Expect.isNonEmpty runtimes "should have found at least the currently-executing runtime" + } + test "can get sdks" { + let sdks = SdkDiscovery.sdks Paths.dotnetRoot + Expect.isNonEmpty sdks "should have found at least the currently-executing sdk" + } ]