diff --git a/build/Compile.targets b/build/Compile.targets
index b2ebb2ebc..b44ed8380 100644
--- a/build/Compile.targets
+++ b/build/Compile.targets
@@ -12,5 +12,7 @@
+
+
diff --git a/build/DependencyVersions.props b/build/DependencyVersions.props
index 64284b4d5..e025ef04a 100644
--- a/build/DependencyVersions.props
+++ b/build/DependencyVersions.props
@@ -2,7 +2,7 @@
2.0.0-preview1-002101-00
- 15.2.0-preview-000093-02
+ 15.3.0-preview-000111-01
2.0.0-rc4-61325-08
2.0.0-alpha-20170428-1
4.3.0-beta1-2418
diff --git a/src/Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj b/src/Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj
index 5d14b7b57..bc95703fb 100644
--- a/src/Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj
+++ b/src/Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj
@@ -20,7 +20,8 @@
-
+
+
@@ -31,4 +32,4 @@
-
\ No newline at end of file
+
diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.Common.cs b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.Common.cs
new file mode 100644
index 000000000..580d1220b
--- /dev/null
+++ b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.Common.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.NETFramework.cs b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.NETFramework.cs
new file mode 100644
index 000000000..d1d567e8b
--- /dev/null
+++ b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.NETFramework.cs
@@ -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
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.NETStandard.cs b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.NETStandard.cs
new file mode 100644
index 000000000..bff6fd84b
--- /dev/null
+++ b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.NETStandard.cs
@@ -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
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs b/src/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs
new file mode 100644
index 000000000..fefbd46d0
--- /dev/null
+++ b/src/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs
@@ -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 GetDotnetExeDirectoryCandidates()
+ {
+ string environmentOverride = Environment.GetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR");
+ if (environmentOverride != null)
+ {
+ return new List(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(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;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj b/src/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj
new file mode 100644
index 000000000..b6399659d
--- /dev/null
+++ b/src/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj
@@ -0,0 +1,38 @@
+
+
+
+
+ $(SdkVersion)
+ netstandard1.3;net46
+ netstandard1.3
+ AnyCPU
+ win-x86;win-x64
+ true
+ ../../tools/Key.snk
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ x86/hostfxr.dll
+ PreserveNewest
+
+
+ x64/hostfxr.dll
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.sln b/src/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.sln
new file mode 100644
index 000000000..4d5062122
--- /dev/null
+++ b/src/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.sln
@@ -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
diff --git a/test/Microsoft.DotNet.Cli.Tests.sln b/test/Microsoft.DotNet.Cli.Tests.sln
index 30e4e2f60..ffc08cbd5 100644
--- a/test/Microsoft.DotNet.Cli.Tests.sln
+++ b/test/Microsoft.DotNet.Cli.Tests.sln
@@ -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
diff --git a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs
new file mode 100644
index 000000000..bfbf14e4a
--- /dev/null
+++ b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnMSBuildSdkResolver.cs
@@ -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())}");
+ _logger.WriteLine($"warnings: {string.Join(Environment.NewLine, result.Warnings ?? Array.Empty())}");
+ _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 errors, IEnumerable warnings = null)
+ => new MockResult { Success = false, Errors = errors, Warnings = warnings };
+
+ public override SdkResult IndicateSuccess(string path, string version, IEnumerable 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 Errors { get; set; }
+ public IEnumerable Warnings { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj
new file mode 100644
index 000000000..e302505e4
--- /dev/null
+++ b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+
+ net46;$(CliTargetFramework)
+ $(CliTargetFramework)
+ $(CLI_SharedFrameworkVersion)
+ Exe
+ ../../tools/Key.snk
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/xunit.runner.json b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/xunit.runner.json
new file mode 100644
index 000000000..34b2fe2cd
--- /dev/null
+++ b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/xunit.runner.json
@@ -0,0 +1,3 @@
+{
+ "shadowCopy": false
+}
\ No newline at end of file