Add msbuild sdk resolver

This commit is contained in:
Nick Guerrera 2017-04-26 15:01:59 -07:00
parent 38b3e21c99
commit d26b41a0c3
13 changed files with 429 additions and 3 deletions

View file

@ -12,5 +12,7 @@
<!-- Publish DotNet -->
<MSBuild Projects="$(SrcDirectory)/redist/redist.csproj"
Targets="Publish" />
<MSBuild Projects="$(SrcDirectory)/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj" />
</Target>
</Project>

View file

@ -2,7 +2,7 @@
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CLI_SharedFrameworkVersion>2.0.0-preview1-002101-00</CLI_SharedFrameworkVersion>
<CLI_MSBuild_Version>15.2.0-preview-000093-02</CLI_MSBuild_Version>
<CLI_MSBuild_Version>15.3.0-preview-000111-01</CLI_MSBuild_Version>
<CLI_Roslyn_Version>2.0.0-rc4-61325-08</CLI_Roslyn_Version>
<CLI_NETSDK_Version>2.0.0-alpha-20170428-1</CLI_NETSDK_Version>
<CLI_NuGet_Version>4.3.0-beta1-2418</CLI_NuGet_Version>

View file

@ -20,7 +20,8 @@
<PackageReference Include="NuGet.ProjectModel" Version="$(CLI_NuGet_Version)" />
<PackageReference Include="Microsoft.Build" Version="$(CLI_MSBuild_Version)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(CLI_MSBuild_Version)" />
</ItemGroup>
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.0.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.5' ">
<PackageReference Include="System.Diagnostics.Process" Version="4.1.0" />
@ -31,4 +32,4 @@
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>
</Project>

View file

@ -0,0 +1,36 @@
// 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.Diagnostics;
using System.Text;
namespace Microsoft.DotNet.MSBuildSdkResolver
{
internal static partial class Interop
{
internal static string hostfxr_resolve_sdk(string exe_dir, string working_dir)
{
var buffer = new StringBuilder(capacity: 64);
for (;;)
{
int size = hostfxr_resolve_sdk(exe_dir, working_dir, buffer, buffer.Capacity);
if (size <= 0)
{
Debug.Assert(size == 0);
return null;
}
if (size <= buffer.Capacity)
{
break;
}
buffer.Capacity = size;
}
return buffer.ToString();
}
}
}

View file

@ -0,0 +1,45 @@
// 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.
#if NET46
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.DotNet.MSBuildSdkResolver
{
internal static partial class Interop
{
static Interop()
{
PreloadLibrary("hostfxr.dll");
}
// MSBuild SDK resolvers are required to be AnyCPU, but we have a native dependency and .NETFramework does not
// have a built-in facility for dynamically loading user native dlls for the appropriate platform. We therefore
// preload the version with the correct architecture (from a corresponding sub-folder relative to us) on static
// construction so that subsequent P/Invokes can find it.
private static void PreloadLibrary(string dllFileName)
{
string basePath = Path.GetDirectoryName(typeof(Interop).Assembly.Location);
string architecture = IntPtr.Size == 8 ? "x64" : "x86";
string dllPath = Path.Combine(basePath, architecture, dllFileName);
// return value is intentially ignored as we let the subsequent P/Invokes fail naturally.
LoadLibraryExW(dllPath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
}
// lpFileName passed to LoadLibraryEx must be a full path.
private const int LOAD_WITH_ALTERED_SEARCH_PATH = 0x8;
[DllImport("kernel32", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr LoadLibraryExW(string lpFileName, IntPtr hFile, int dwFlags);
[DllImport("hostfxr", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
private static extern int hostfxr_resolve_sdk(string exe_dir, string working_dir, [Out] StringBuilder buffer, int buffer_size);
}
}
#endif // NET46

View file

@ -0,0 +1,37 @@
// 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.
// NOTE: Currently, only the NET46 build ships (with Visual Studio/desktop msbuild),
// but the netstandard1.3 adaptation here acts a proof-of-concept for cross-platform
// portability of the underlying hostfxr API and gives us build and test coverage
// on non-Windows machines.
#if NETSTANDARD1_3
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.DotNet.MSBuildSdkResolver
{
internal static partial class Interop
{
internal static readonly bool s_runningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static int hostfxr_resolve_sdk(string exe_dir, string working_dir, [Out] StringBuilder buffer, int buffer_size)
{
// hostfxr string encoding is platform -specific so dispatch to the
// appropriately annotated P/Invoke for the current platform.
return s_runningOnWindows
? windows_hostfxr_resolve_sdk(exe_dir, working_dir, buffer, buffer_size)
: unix_hostfxr_resolve_sdk(exe_dir, working_dir, buffer, buffer_size);
}
[DllImport("hostfxr", EntryPoint = nameof(hostfxr_resolve_sdk), CharSet = CharSet.Unicode, ExactSpelling=true, CallingConvention = CallingConvention.Cdecl)]
private static extern int windows_hostfxr_resolve_sdk(string exe_dir, string working_dir, [Out] StringBuilder buffer, int buffer_size);
// CharSet.Ansi is UTF8 on Unix
[DllImport("hostfxr", EntryPoint = nameof(hostfxr_resolve_sdk), CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
private static extern int unix_hostfxr_resolve_sdk(string exe_dir, string working_dir, [Out] StringBuilder buffer, int buffer_size);
}
}
#endif // NETSTANDARD1_3

View file

@ -0,0 +1,123 @@
// 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 Microsoft.Build.Framework;
using System;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.DotNet.MSBuildSdkResolver
{
public sealed class DotNetMSBuildSdkResolver : SdkResolver
{
public override string Name => "Microsoft.DotNet.MSBuildSdkResolver";
// Default resolver has priority 10000 and we want to go before it and leave room on either side of us.
public override int Priority => 5000;
public override SdkResult Resolve(SdkReference sdkReference, SdkResolverContext context, SdkResultFactory factory)
{
// These are overrides that are used to force the resolved SDK tasks and targets to come from a given
// base directory and report a given version to msbuild (which may be null if unknown. One key use case
// for this is to test SDK tasks and targets without deploying them inside the .NET Core SDK.
string msbuildSdksDir = Environment.GetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR");
string netcoreSdkVersion = Environment.GetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_SDKS_VER");
if (msbuildSdksDir == null)
{
string netcoreSdkDir = ResolveNetcoreSdkDirectory(context);
if (netcoreSdkDir == null)
{
return factory.IndicateFailure(
new[]
{
"Unable to locate the .NET Core SDK. Check that it is installed and that the version"
+ "specified in global.json (if any) matches the installed version."
});
}
msbuildSdksDir = Path.Combine(netcoreSdkDir, "Sdks");
netcoreSdkVersion = new DirectoryInfo(netcoreSdkDir).Name;;
}
string msbuildSdkDir = Path.Combine(msbuildSdksDir, sdkReference.Name, "Sdk");
if (!Directory.Exists(msbuildSdkDir))
{
return factory.IndicateFailure(
new[]
{
$"{msbuildSdkDir} not found. Check that a recent enough .NET Core SDK is installed"
+ " and/or increase the version specified in global.json. "
});
}
return factory.IndicateSuccess(msbuildSdkDir, netcoreSdkVersion);
}
private string ResolveNetcoreSdkDirectory(SdkResolverContext context)
{
foreach (string exeDir in GetDotnetExeDirectoryCandidates())
{
string workingDir = context.SolutionFilePath ?? context.ProjectFilePath;
string netcoreSdkDir = Interop.hostfxr_resolve_sdk(exeDir, workingDir);
if (netcoreSdkDir != null)
{
return netcoreSdkDir;
}
}
return null;
}
// Search for [ProgramFiles]\dotnet in this order. Only ProgramFiles is defined on
private static readonly string[] s_programFiles = new[]
{
// "c:\Program Files" on x64 machine regardless process bitness, undefined on x86 machines.
"ProgramW6432",
// "c:\Program Files (x86)" on x64 machine regardless of process bitness, undefined on x64 machines.
"ProgramFiles(x86)",
// "c:\Program Files" in x64 process, "c:\Program Files (x86)" in x86 process.
// 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()
{
string environmentOverride = Environment.GetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR");
if (environmentOverride != null)
{
return new List<string>(1) { 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>(2);
foreach (string variable in s_programFiles)
{
string directory = Environment.GetEnvironmentVariable(variable);
if (directory == null)
{
continue;
}
directory = Path.Combine(directory, "dotnet");
if (!candidates.Contains(directory))
{
candidates.Add(directory);
}
}
if (candidates.Count == 0)
{
candidates.Add(null);
}
return candidates;
}
}
}

View file

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<Version>$(SdkVersion)</Version>
<TargetFrameworks>netstandard1.3;net46</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netstandard1.3</TargetFrameworks>
<PlatformTarget>AnyCPU</PlatformTarget>
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
<WarningsAsErrors>true</WarningsAsErrors>
<AssemblyOriginatorKeyFile>../../tools/Key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="$(CLI_MSBuild_Version)" />
<PackageReference Include="Microsoft.NETCore.DotNetHostResolver" Version="$(CLI_SharedFrameworkVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="NETStandard.Library" Version="1.6.0" />
</ItemGroup>
<Target Name="ResolveHostfxrCopyLocalContent" Condition="'$(TargetFramework)' == 'net46'" DependsOnTargets="ResolvePackageDependenciesForBuild" BeforeTargets="AssignTargetPaths">
<ItemGroup>
<Content Include="@(FileDefinitions->'%(ResolvedPath)')" Condition="'%(FileDefinitions.Path)' == 'runtimes/win-x86/native/hostfxr.dll'">
<Link>x86/hostfxr.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="@(FileDefinitions->'%(ResolvedPath)')" Condition="'%(FileDefinitions.Path)' == 'runtimes/win-x64/native/hostfxr.dll'">
<Link>x64/hostfxr.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Target>
</Project>

View file

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26425.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MSBuildSdkResolver", "Microsoft.DotNet.MSBuildSdkResolver.csproj", "{DCB2A518-7BC6-43F5-BE2C-13B11A1F3961}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MSBuildSdkResolver.Tests", "..\..\test\Microsoft.DotNet.MSBuildSdkResolver.Tests\Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj", "{CC488F39-E106-4BF4-9599-19A265AFD9AC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DCB2A518-7BC6-43F5-BE2C-13B11A1F3961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCB2A518-7BC6-43F5-BE2C-13B11A1F3961}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCB2A518-7BC6-43F5-BE2C-13B11A1F3961}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCB2A518-7BC6-43F5-BE2C-13B11A1F3961}.Release|Any CPU.Build.0 = Release|Any CPU
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View file

@ -76,6 +76,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-store.Tests", "dotne
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-back-compat.Tests", "dotnet-back-compat.Tests\dotnet-back-compat.Tests.csproj", "{27351B2F-325B-4843-9F4C-BC53FD06A7B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MSBuildSdkResolver.Tests", "Microsoft.DotNet.MSBuildSdkResolver.Tests\Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj", "{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -506,6 +508,18 @@ Global
{27351B2F-325B-4843-9F4C-BC53FD06A7B5}.Release|x64.Build.0 = Release|Any CPU
{27351B2F-325B-4843-9F4C-BC53FD06A7B5}.Release|x86.ActiveCfg = Release|Any CPU
{27351B2F-325B-4843-9F4C-BC53FD06A7B5}.Release|x86.Build.0 = Release|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Debug|x64.ActiveCfg = Debug|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Debug|x64.Build.0 = Debug|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Debug|x86.ActiveCfg = Debug|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Debug|x86.Build.0 = Debug|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Release|Any CPU.Build.0 = Release|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Release|x64.ActiveCfg = Release|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Release|x64.Build.0 = Release|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Release|x86.ActiveCfg = Release|Any CPU
{42A0CAB4-FFAD-47D4-9880-C0F4EDCF93DE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -0,0 +1,70 @@
// 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.Collections.Generic;
using Microsoft.Build.Framework;
using Xunit;
using System.Linq;
using Xunit.Abstractions;
using System;
using Microsoft.DotNet.MSBuildSdkResolver;
namespace Microsoft.DotNet.Cli.Utils.Tests
{
public class GivenAnMSBuildSdkResolver
{
private ITestOutputHelper _logger;
public GivenAnMSBuildSdkResolver(ITestOutputHelper logger)
{
_logger = logger;
}
[Fact]
public void ItHasCorrectNameAndPriority()
{
var resolver = new DotNetMSBuildSdkResolver();
Assert.Equal(5000, resolver.Priority);
Assert.Equal("Microsoft.DotNet.MSBuildSdkResolver", resolver.Name);
}
[Fact]
public void ItCallsNativeCodeWithoutCrashing() // WIP: placeholder to get plumbing through
{
var resolver = new DotNetMSBuildSdkResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Microsoft.NET.Sdk", null, null),
new MockContext(),
new MockFactory());
_logger.WriteLine($"success: {result.Success}");
_logger.WriteLine($"errors: {string.Join(Environment.NewLine, result.Errors ?? Array.Empty<string>())}");
_logger.WriteLine($"warnings: {string.Join(Environment.NewLine, result.Warnings ?? Array.Empty<string>())}");
_logger.WriteLine($"path: {result.Path}");
_logger.WriteLine($"version: {result.Version}");
}
private sealed class MockContext : SdkResolverContext
{
}
private sealed class MockFactory : SdkResultFactory
{
public override SdkResult IndicateFailure(IEnumerable<string> errors, IEnumerable<string> warnings = null)
=> new MockResult { Success = false, Errors = errors, Warnings = warnings };
public override SdkResult IndicateSuccess(string path, string version, IEnumerable<string> warnings = null)
=> new MockResult { Success = true, Path = path, Version = version, Warnings = warnings };
}
private sealed class MockResult : SdkResult
{
public new bool Success { get => base.Success; set => base.Success = value; }
public string Version { get; set; }
public string Path { get; set; }
public IEnumerable<string> Errors { get; set; }
public IEnumerable<string> Warnings { get; set; }
}
}
}

View file

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<TargetFrameworks>net46;$(CliTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">$(CliTargetFramework)</TargetFrameworks>
<RuntimeFrameworkVersion>$(CLI_SharedFrameworkVersion)</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType>
<AssemblyOriginatorKeyFile>../../tools/Key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.DotNet.MSBuildSdkResolver\Microsoft.DotNet.MSBuildSdkResolver.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="runtime.linux-x64.Microsoft.NETCore.DotNetHostResolver" Version="$(CLI_SharedFrameworkVersion)" />
<PackageReference Include="runtime.osx-x64.Microsoft.NETCore.DotNetHostResolver" Version="$(CLI_SharedFrameworkVersion)" />
</ItemGroup>
<ItemGroup>
<None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,3 @@
{
"shadowCopy": false
}