Skip to content
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

ResolveDotNetRoots to support resoving multiple .NET version paths #2832

Merged
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
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
## 6.1.2 - 2024-09-20
* Runtime-identifier update to more recent runtimes.
* Removed .NETFrameworkAssembly attribute fix on detecting .NET assemblies.
* SdkAssemblyResolver: Support detecting multiple .NET paths.

## 6.1.1 - 2024-08-30
* BUGFIX: Assembly resolver to pick a found SDK instead of first SDK, thanks @Thorium - https://github.com/fsprojects/FAKE/pull/2797/files
Expand Down
119 changes: 113 additions & 6 deletions src/app/Fake.Runtime/SdkAssemblyResolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,89 @@ type SdkAssemblyResolver(logLevel: Trace.VerboseLevel) =
|> Option.bind resolveFile
|> Option.map (fun dotnetRoot -> dotnetRoot.Directory.FullName)

/// <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>.
/// Also probes the PATH and checks the default installation locations
/// </summary>
member this.ResolveDotNetRoots() =
let isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
let isMac = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
let isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
let isUnix = isLinux || isMac

let potentialDotnetHostEnvVars =
[ "DOTNET_HOST_PATH", id // is a full path to dotnet binary
"DOTNET_ROOT", (fun s -> Path.Combine(s, this.DotNetBinaryName)) // needs dotnet binary appended
"DOTNET_ROOT(x86)", (fun s -> Path.Combine(s, this.DotNetBinaryName)) ] // needs dotnet binary appended
Thorium marked this conversation as resolved.
Show resolved Hide resolved

let existingEnvVarValue envVarValue =
match envVarValue with
| null
| "" -> None
| other -> Some other

let tryFindFromEnvVar () =
potentialDotnetHostEnvVars
|> List.choose (fun (envVar, transformer) ->
match Environment.GetEnvironmentVariable envVar |> existingEnvVarValue with
| Some varValue -> Some(transformer varValue |> FileInfo)
| None -> None)
Thorium marked this conversation as resolved.
Show resolved Hide resolved

let PATHSeparator = if isUnix then ':' else ';'

/// Fully resolve the symlink, returning a fully-resolved FileInfo
/// that is not a symlink,
/// or returning None if the file or any of its resolved targets
/// does not exist.
let rec resolveFile (fi: System.IO.FileInfo) : FileInfo option =
if not fi.Exists then None
elif isNull fi.LinkTarget then Some fi
else resolveFile (System.IO.FileInfo fi.LinkTarget)

let tryFindFromPATH () =
Environment
.GetEnvironmentVariable("PATH")
.Split(PATHSeparator, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries)
|> Array.choose (fun d ->
let fi = Path.Combine(d, this.DotNetBinaryName) |> FileInfo

if fi.Exists then Some fi else None)
Thorium marked this conversation as resolved.
Show resolved Hide resolved

let tryFindFromDefaultDirs () =
let windowsPath = $"C:\\Program Files\\dotnet\\{this.DotNetBinaryName}"
let macosPath = $"/usr/local/share/dotnet/{this.DotNetBinaryName}"
let linuxPath = $"/usr/share/dotnet/{this.DotNetBinaryName}"

let tryFindFile p =
let f = FileInfo p

if f.Exists then Some f else None

if isWindows then tryFindFile windowsPath
else if isMac then tryFindFile macosPath
else if isLinux then tryFindFile linuxPath
else None

if not (String.isNullOrEmpty CustomDotNetHostPath) then
[| CustomDotNetHostPath |]
else
let paths =
seq {
yield! tryFindFromEnvVar ()
yield! tryFindFromPATH ()

match tryFindFromDefaultDirs () with
| Some x -> yield x
| None -> ()
}

paths
|> Seq.distinct
|> Seq.choose resolveFile
|> Seq.map (fun dotnetRoot -> dotnetRoot.Directory.FullName)
|> Seq.distinct
|> Seq.toArray

member this.TryResolveSdkRuntimeVersionFromNetwork() =
if this.LogLevel.PrintVerbose then
Trace.tracefn "Trying to resolve runtime version from network.."
Expand Down Expand Up @@ -217,24 +300,48 @@ type SdkAssemblyResolver(logLevel: Trace.VerboseLevel) =
this.GetProductReleasesForSdk version |> List.tryHead

member this.ResolveSdkRuntimeVersions() =
let versionOptions (options: DotNet.VersionOptions) =
let versionOptions dotnetRoot (options: DotNet.VersionOptions) =
// If a custom CLI path is provided, configure the version command
// to use that path. This really only accomodates a test scenarios
// in which FAKE_SDK_RESOLVER_CUSTOM_DOTNET_PATH is set.
match this.ResolveDotNetRoot() with
match dotnetRoot with
| Some root ->
options.WithCommon(fun common -> { common with DotNetCliPath = root </> this.DotNetBinaryName })
| None -> options

let sdkVersion = DotNet.getVersion versionOptions |> ReleaseVersion
let dotnetRoots = this.ResolveDotNetRoots()

let sdkVersions =
if Array.isEmpty dotnetRoots then
[ DotNet.getVersion (versionOptions None) |> ReleaseVersion ]
else
dotnetRoots
|> Seq.map (fun dotnetRoot -> DotNet.getVersion (versionOptions (Some dotnetRoot)) |> ReleaseVersion)
|> Seq.toList

let productReleases =
sdkVersions
|> List.collect (fun sdkVersion -> this.GetProductReleasesForSdk sdkVersion)

match this.GetProductReleasesForSdk sdkVersion with
match productReleases with
| [] ->
let versions =
String.Join(", ", sdkVersions |> Seq.map (fun sdkVersion -> sdkVersion.ToString()))

let example =
sdkVersions
|> Seq.tryHead
|> Option.map (fun sdkVersion -> sdkVersion.Major.ToString())
|> Option.defaultValue "6"

failwithf
$"Could not find a suitable .NET 6 runtime version matching SDK version: {sdkVersion.ToString()} (You can also try setting environment variable FAKE_SDK_RESOLVER_CUSTOM_DOTNET_VERSION to e.g. {sdkVersion.Major.ToString()}.0 )"
$"Could not find a suitable .NET 6 runtime version matching SDK version: {versions} (You can also try setting environment variable FAKE_SDK_RESOLVER_CUSTOM_DOTNET_VERSION to e.g. {example}.0 )"
| releases ->
let versions =
releases |> List.map (fun release -> release.Runtime.Version.ToString())
releases
|> Seq.map (fun release -> release.Runtime.Version.ToString())
|> Seq.distinct
|> Seq.toList

if this.LogLevel.PrintVerbose then
versions
Expand Down
Loading