Changing the resolver so that it will search for dotnet in the PATH instead of hard coded program files.

This commit is contained in:
Livar Cunha 2017-05-22 23:09:32 -07:00
parent cf28ca657a
commit 5b3cd63198
3 changed files with 107 additions and 68 deletions

View file

@ -0,0 +1,78 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Microsoft.DotNet.MSBuildSdkResolver
{
internal class EnvironmentProvider
{
private IEnumerable<string> _searchPaths;
private IEnumerable<string> _executableExtensions;
private readonly Func<string, string> _getEnvironmentVariable;
public EnvironmentProvider(Func<string, string> getEnvironmentVariable)
{
_getEnvironmentVariable = getEnvironmentVariable;
}
public IEnumerable<string> ExecutableExtensions
{
get
{
if (_executableExtensions == null)
{
_executableExtensions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? _getEnvironmentVariable("PATHEXT")
.Split(';')
.Select(e => e.ToLower().Trim('"'))
: new [] { string.Empty };
}
return _executableExtensions;
}
}
private IEnumerable<string> SearchPaths
{
get
{
if (_searchPaths == null)
{
var searchPaths = new List<string> { GetApplicationBasePath() };
searchPaths.AddRange(
_getEnvironmentVariable("PATH")
.Split(Path.PathSeparator)
.Select(p => p.Trim('"')));
_searchPaths = searchPaths;
}
return _searchPaths;
}
}
public string GetCommandPath(string commandName)
{
var commandPath = SearchPaths.Join(
ExecutableExtensions.ToArray(),
p => true, s => true,
(p, s) => Path.Combine(p, commandName + s))
.FirstOrDefault(File.Exists);
return commandPath;
}
private static string GetApplicationBasePath()
{
return Path.GetFullPath(AppContext.BaseDirectory);
}
}
}

View file

@ -106,71 +106,24 @@ namespace Microsoft.DotNet.MSBuildSdkResolver
private string ResolveNetcoreSdkDirectory(SdkResolverContext context)
{
foreach (string exeDir in GetDotnetExeDirectoryCandidates())
{
string workingDir = context.SolutionFilePath ?? context.ProjectFilePath;
string netcoreSdkDir = Interop.hostfxr_resolve_sdk(exeDir, workingDir);
string exeDir = GetDotnetExeDirectoryCandidates();
string workingDir = context.SolutionFilePath ?? context.ProjectFilePath;
string netcoreSdkDir = Interop.hostfxr_resolve_sdk(exeDir, workingDir);
if (netcoreSdkDir != null)
{
return netcoreSdkDir;
}
}
return null;
return netcoreSdkDir;
}
// Search for [ProgramFiles]\dotnet in this order.
private static readonly string[] s_programFiles = new[]
{
// "c:\Program Files" on x64 machine regardless process architecture.
// Undefined on x86 machines.
"ProgramW6432",
// "c:\Program Files (x86)" on x64 machine regardless of process architecture
// Undefined on x86 machines.
"ProgramFiles(x86)",
// "c:\Program Files" or "C:\Program Files (x86)" on x64 machine depending on process architecture.
// "c:\Program Files" on x86 machines (therefore not redundant with the two locations above in that case).
//
// NOTE: hostfxr will search this on its own if multilevel lookup is not disable, but we do it explicitly
// to prevent an environment with disabled multilevel lookup from crippling desktop msbuild and VS.
"ProgramFiles",
};
private List<string> GetDotnetExeDirectoryCandidates()
private string GetDotnetExeDirectoryCandidates()
{
string environmentOverride = _getEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR");
if (environmentOverride != null)
{
return new List<string>(capacity: 1) { environmentOverride };
return environmentOverride;
}
// Initial capacity is 2 because while there are 3 candidates, we expect at most 2 unique ones (x64 + x86)
// Also, N=3 here means that we needn't be concerned with the O(N^2) complexity of the foreach + contains.
var candidates = new List<string>(capacity: 2);
foreach (string variable in s_programFiles)
{
string directory = _getEnvironmentVariable(variable);
if (directory == null)
{
continue;
}
var environmentProvider = new EnvironmentProvider(_getEnvironmentVariable);
directory = Path.Combine(directory, "dotnet");
if (!candidates.Contains(directory))
{
candidates.Add(directory);
}
}
if (candidates.Count == 0)
{
candidates.Add(null);
}
return candidates;
return Path.GetDirectoryName(environmentProvider.GetCommandPath("dotnet"));
}
}
}

View file

@ -8,6 +8,7 @@ using Microsoft.DotNet.Tools.Test.Utilities;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Xunit;
using Xunit.Abstractions;
using System;
@ -40,6 +41,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
var expected = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.98");
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99");
environment.CreateGlobalJson(environment.TestDirectory, "99.99.98");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
@ -122,6 +124,10 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
private sealed class TestEnvironment : SdkResolverContext
{
public string Muxer => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet";
public string PathEnvironmentVariable { get; set; }
public DirectoryInfo TestDirectory { get; }
public TestEnvironment(string identifier = "", [CallerMemberName] string callingMethod = "")
@ -130,6 +136,8 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
"temp",
identifier: identifier,
callingMethod: callingMethod);
PathEnvironmentVariable = string.Empty;
}
public SdkResolver CreateResolver()
@ -148,30 +156,30 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
return dir;
}
public void CreateMuxerAndAddToPath(ProgramFiles programFiles)
{
var muxerDirectory = TestDirectory.GetDirectory(GetProgramFilesDirectory(programFiles).FullName, "dotnet");
new FileInfo(Path.Combine(muxerDirectory.FullName, Muxer)).Create();
PathEnvironmentVariable = $"{muxerDirectory}{Path.PathSeparator}{PathEnvironmentVariable}";
}
public void CreateGlobalJson(DirectoryInfo directory, string version)
=> File.WriteAllText(directory.GetFile("global.json").FullName,
$@"{{ ""sdk"": {{ ""version"": ""{version}"" }} }}");
public string GetEnvironmentVariable(string variable)
{
ProgramFiles programFiles;
switch (variable)
{
case "ProgramW6432":
programFiles = ProgramFiles.X64;
break;
case "ProgramFiles(x86)":
programFiles = ProgramFiles.X86;
break;
case "ProgramFiles":
programFiles = ProgramFiles.Default;
break;
case "PATH":
return PathEnvironmentVariable;
case "PATHEX":
return ".exe";
default:
return null;
}
return GetProgramFilesDirectory(programFiles).FullName;
}
}