Skip to content

Commit

Permalink
add in support for more dotnet environment variables (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
baronfel authored Oct 17, 2021
1 parent 823fe86 commit 1c9317b
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"paket": {
"version": "6.0.0-rc006",
"version": "6.2.1",
"commands": [
"paket"
]
Expand Down
19 changes: 9 additions & 10 deletions .paket/Paket.Restore.targets
Original file line number Diff line number Diff line change
Expand Up @@ -236,17 +236,16 @@
<PackageName>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])</PackageName>
<PackageVersion>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])</PackageVersion>
<AllPrivateAssets>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])</AllPrivateAssets>
<CopyLocal Condition="'%(PaketReferencesFileLinesInfo.Splits)' == '6'">$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])</CopyLocal>
<OmitContent Condition="'%(PaketReferencesFileLinesInfo.Splits)' == '7'">$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6])</OmitContent>
<ImportTargets Condition="'%(PaketReferencesFileLinesInfo.Splits)' == '8'">$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7])</ImportTargets>
<CopyLocal Condition="%(PaketReferencesFileLinesInfo.Splits) &gt;= 6">$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])</CopyLocal>
<OmitContent Condition="%(PaketReferencesFileLinesInfo.Splits) &gt;= 7">$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6])</OmitContent>
<ImportTargets Condition="%(PaketReferencesFileLinesInfo.Splits) &gt;= 8">$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7])</ImportTargets>
</PaketReferencesFileLinesInfo>
<PackageReference Include="%(PaketReferencesFileLinesInfo.PackageName)">
<Version>%(PaketReferencesFileLinesInfo.PackageVersion)</Version>
<PrivateAssets Condition=" ('%(PaketReferencesFileLinesInfo.AllPrivateAssets)' == 'true') Or ('$(PackAsTool)' == 'true') ">All</PrivateAssets>
<ExcludeAssets Condition=" '%(PaketReferencesFileLinesInfo.Splits)' == '6' And %(PaketReferencesFileLinesInfo.CopyLocal) == 'false'">runtime</ExcludeAssets>
<ExcludeAssets Condition=" '%(PaketReferencesFileLinesInfo.Splits)' != '6' And %(PaketReferencesFileLinesInfo.AllPrivateAssets) == 'exclude'">runtime</ExcludeAssets>
<ExcludeAssets Condition=" '%(PaketReferencesFileLinesInfo.Splits)' != '6' And %(PaketReferencesFileLinesInfo.OmitContent) == 'true'">$(ExcludeAssets);contentFiles</ExcludeAssets>
<ExcludeAssets Condition=" '%(PaketReferencesFileLinesInfo.Splits)' != '6' And %(PaketReferencesFileLinesInfo.ImportTargets) == 'false'">$(ExcludeAssets);build;buildMultitargeting;buildTransitive</ExcludeAssets>
<ExcludeAssets Condition=" %(PaketReferencesFileLinesInfo.CopyLocal) == 'false' or %(PaketReferencesFileLinesInfo.AllPrivateAssets) == 'exclude'">runtime</ExcludeAssets>
<ExcludeAssets Condition=" %(PaketReferencesFileLinesInfo.OmitContent) == 'true'">$(ExcludeAssets);contentFiles</ExcludeAssets>
<ExcludeAssets Condition=" %(PaketReferencesFileLinesInfo.ImportTargets) == 'false'">$(ExcludeAssets);build;buildMultitargeting;buildTransitive</ExcludeAssets>
<Publish Condition=" '$(PackAsTool)' == 'true' ">true</Publish>
<AllowExplicitVersion>true</AllowExplicitVersion>
</PackageReference>
Expand Down Expand Up @@ -366,9 +365,9 @@
PackageLicenseFile="$(PackageLicenseFile)"
PackageLicenseExpression="$(PackageLicenseExpression)"
PackageLicenseExpressionVersion="$(PackageLicenseExpressionVersion)"
PackageReadmeFile="$(PackageReadmeFile)"
NoDefaultExcludes="$(NoDefaultExcludes) "/>
Readme="$(PackageReadmeFile)"
NoDefaultExcludes="$(NoDefaultExcludes)"/>

<PackTask Condition="$(UseMSBuild16_0_Pack)"
PackItem="$(PackProjectInputFile)"
PackageFiles="@(_PackageFiles)"
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# Changelog

## [0.54.1] - 2021-10-16

### Fixed

- Added more environment variable lookups for the `dotnet` binary, so modes like running under `dotnet test` should work more consistently.

## [0.54.0] - 2021-08-08
### Added

Expand Down
4 changes: 2 additions & 2 deletions src/Ionide.ProjInfo.Tool/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ type LoaderFunc = ToolsPath * list<string * string> -> 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)

Expand Down
136 changes: 94 additions & 42 deletions src/Ionide.ProjInfo/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

[<RequireQualifiedAccess>]
module Init =
Expand Down Expand Up @@ -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
Expand All @@ -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}"

[<RequireQualifiedAccess>]
Expand Down
53 changes: 41 additions & 12 deletions src/Ionide.ProjInfo/Utils.fs
Original file line number Diff line number Diff line change
@@ -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

/// <summary>
/// provides the path to the `dotnet` binary running this library, respecting various dotnet <see href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#dotnet_root-dotnet_rootx86%5D">environment variables</see>
/// </summary>
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")
Expand Down
11 changes: 1 addition & 10 deletions test/Ionide.ProjInfo.Tests/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,7 @@ open Expecto.Logging

[<EntryPoint>]
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
Expand Down
10 changes: 9 additions & 1 deletion test/Ionide.ProjInfo.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
} ]

0 comments on commit 1c9317b

Please sign in to comment.