diff --git a/.gitignore b/.gitignore index 847693d1d..397d2babe 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,9 @@ bin/ *.svclog *.scc +# BinLog artifacts +msbuild.*.ProjectImports.zip + # Chutzpah Test files _Chutzpah* diff --git a/Documentation/general/signing-global-tool-packages.md b/Documentation/general/signing-global-tool-packages.md new file mode 100644 index 000000000..5ecb910a1 --- /dev/null +++ b/Documentation/general/signing-global-tool-packages.md @@ -0,0 +1,60 @@ +Signing .NET Core Global Tool Packages +=============================== + +To create a signed package for your Dotnet Tool, you will need to create a signed shim. If a shim is found in the nupkg during `dotnet tool install`, it is used instead of creating one on consumer's machine. + +To create a signed shim, you need to add the following extra property in you project file: + +``` + [list of RIDs] +``` + +When this property is set, `dotnet pack` will generate a shim in the package (nupkg). Assuming all other other content is signed, after you sign the shim you can sign the nupkg. + +Example: +```xml + + + + Exe + netcoreapp2.1 + true + win-x64;win-x86;osx-x64 + + +``` + +The result nupkg will have packaged shim included. Of course `dotnet pack` does not sign the shim or the package. The mechanism for that depends on your processes. The structure of the unzipped nupkg is: + +``` +│ shimexample.nuspec +│ [Content_Types].xml +│ +├───package +│ └───services +│ └───metadata +│ └───core-properties +│ 9c20d06e1d8b4a4ba3e126f30013ef32.psmdcp +│ +├───tools +│ └───netcoreapp2.1 +│ └───any +│ │ DotnetToolSettings.xml +│ │ shimexample.deps.json +│ │ shimexample.dll +│ │ shimexample.pdb +│ │ shimexample.runtimeconfig.json +│ │ +│ └───shims +│ ├───osx-x64 +│ │ shimexample +│ │ +│ ├───win-x64 +│ │ shimexample.exe +│ │ +│ └───win-x86 +│ shimexample.exe +│ +└───_rels + .rels +``` diff --git a/Documentation/general/tool-nuget-package-format.md b/Documentation/general/tool-nuget-package-format.md new file mode 100644 index 000000000..0fd0f39b6 --- /dev/null +++ b/Documentation/general/tool-nuget-package-format.md @@ -0,0 +1,29 @@ +Tool NuGet package format +------------------------------------------- + +The most straightforward way to create a .NET tool package is to run `dotnet pack` with `PackAsTool` property set in the project file. However, if your build process is highly customized, `dotnet publish` may not create the right package for you. In this case, you can create a NuGet package for your tool using a *nuspec* file and explicitly placing assets into the NuGet package following these rules. + +- The NuGet package has only _/tools_ folder under the root and does **not** contain any other folders; do not include folders like _/lib_, _/content_, etc. +- Under _/tools_ folder, the subfolders must be structured with pattern _target framework short version/RID_. For example, tool assets targeting .NET core framework V2.1 that are portable across platforms should be in the folder _tools/netcoreapp2.1/any_. + +Let's call assets under every _tools/target framework short version/RID_ combination "per TFM-RID assets" : +- There is a DotnetToolSettings.xml for every "per TFM-RID assets". +- The package type is DotnetTool. +- Each set of TFM-RID assets should have all the dependencies the tool requires to run. The TFM-RID assets should work correctly after being copied via `xcopy` to another machine, assuming that machine has the correct runtime version and RID environment. +- For portable app, there must be runtimeconfig.json for every "per TFM-RID assets". + +# Remark: +- Currently, only portable apps are supported so the RID must be _any_. +- Only one tool per tool package. + +DotnetToolSettings.xml: +Example: +```xml + + + + + + +``` +Currently only configurable part is command name: _sayhello_ and entry point: _console.dll_. Command Name is what the user will type in their shell to invoke the command. Entry point is the relative path to the entry dll with main. diff --git a/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.csproj b/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.csproj index 8f6e579e7..0da9b9949 100644 --- a/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.csproj +++ b/TestAssets/TestPackages/dotnet-dependency-tool-invoker/dotnet-dependency-tool-invoker.csproj @@ -1,5 +1,9 @@ - - + + + + + 1.0.0-rc @@ -16,4 +20,5 @@ + diff --git a/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedCS/MultitargetedCS.csproj b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedCS/MultitargetedCS.csproj new file mode 100644 index 000000000..988358c8a --- /dev/null +++ b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedCS/MultitargetedCS.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp2.2 + + + diff --git a/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedCS/Program.cs b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedCS/Program.cs new file mode 100644 index 000000000..83d14f592 --- /dev/null +++ b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedCS/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace MultitargetedCS +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedFS/MultitargetedFS.fsproj b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedFS/MultitargetedFS.fsproj new file mode 100644 index 000000000..2d0082582 --- /dev/null +++ b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedFS/MultitargetedFS.fsproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.2 + + + + + + + diff --git a/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedFS/Program.fs b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedFS/Program.fs new file mode 100644 index 000000000..a7458f522 --- /dev/null +++ b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedFS/Program.fs @@ -0,0 +1,8 @@ +// Learn more about F# at http://fsharp.org + +open System + +[] +let main argv = + printfn "Hello World from F#!" + 0 // return an integer exit code diff --git a/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedVB/MultitargetedVB.vbproj b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedVB/MultitargetedVB.vbproj new file mode 100644 index 000000000..da41d91f8 --- /dev/null +++ b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedVB/MultitargetedVB.vbproj @@ -0,0 +1,9 @@ + + + + Exe + MultitargetedVB + netcoreapp2.2 + + + diff --git a/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedVB/Program.vb b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedVB/Program.vb new file mode 100644 index 000000000..46283ca23 --- /dev/null +++ b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/MultitargetedVB/Program.vb @@ -0,0 +1,7 @@ +Imports System + +Module Program + Sub Main(args As String()) + Console.WriteLine("Hello World!") + End Sub +End Module diff --git a/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/TestAppsWithSlnAndMultitargetedProjects.sln b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/TestAppsWithSlnAndMultitargetedProjects.sln new file mode 100644 index 000000000..2f92ebd9b --- /dev/null +++ b/TestAssets/TestProjects/TestAppsWithSlnAndMultitargetedProjects/TestAppsWithSlnAndMultitargetedProjects.sln @@ -0,0 +1,18 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestWebAppSimple/TestWebAppSimple.csproj b/TestAssets/TestProjects/TestWebAppSimple/TestWebAppSimple.csproj index a6a28607a..a4ce1e1bc 100644 --- a/TestAssets/TestProjects/TestWebAppSimple/TestWebAppSimple.csproj +++ b/TestAssets/TestProjects/TestWebAppSimple/TestWebAppSimple.csproj @@ -13,11 +13,18 @@ - + + + + + - + diff --git a/build/BuildDefaults.props b/build/BuildDefaults.props index 0a270a647..602b1970d 100644 --- a/build/BuildDefaults.props +++ b/build/BuildDefaults.props @@ -34,6 +34,7 @@ NU1701;NU5104 true + true true false diff --git a/build/BundledTemplates.props b/build/BundledTemplates.props index c86fe766b..e3e280bbf 100644 --- a/build/BundledTemplates.props +++ b/build/BundledTemplates.props @@ -1,8 +1,8 @@ - - + + diff --git a/build/DependencyVersions.props b/build/DependencyVersions.props index ca660bc0d..4d014990b 100644 --- a/build/DependencyVersions.props +++ b/build/DependencyVersions.props @@ -18,13 +18,16 @@ 3.0.0-preview1-26909-04 - $(MicrosoftNETCoreAppPackageVersion) + + + 3.0.0-preview1-26816-04 + - 1.0.2-beta3 - $(MicrosoftDotNetCommonItemTemplatesPackageVersion) - 1.0.2-beta3-20180716-1864993 + 1.0.2-beta4-20180803-1918431 + $(MicrosoftDotNetCommonItemTemplatesPackageVersion) + 1.0.2-beta4-20180821-1966911 0.2.0 1.5.1 @@ -48,7 +51,7 @@ 2.1.0-prerelease-02430-04 $(BuildTasksFeedToolVersion) - 2.0.0-preview2-25331-01 + 2.0.0 diff --git a/build/NugetConfigFile.targets b/build/NugetConfigFile.targets index ce1624044..8929e74dd 100644 --- a/build/NugetConfigFile.targets +++ b/build/NugetConfigFile.targets @@ -34,6 +34,7 @@ + ]]> diff --git a/build/Test.targets b/build/Test.targets index 9829573cd..a52cb1f48 100644 --- a/build/Test.targets +++ b/build/Test.targets @@ -10,6 +10,7 @@ $(TestOutputDir)/packages/ $(TestOutputDir)/artifacts/ $(TestOutputDir)/results/ + $(TestArtifactsDir)/ExternalRestoreSourcesForTestsContainer.txt + + + - - - - - - - - - + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + @@ -184,8 +196,9 @@ - - + + + diff --git a/build/package/Installer.RPM.targets b/build/package/Installer.RPM.targets index dc8c27e2c..acb7531d6 100644 --- a/build/package/Installer.RPM.targets +++ b/build/package/Installer.RPM.targets @@ -197,7 +197,8 @@ - + + diff --git a/build_projects/dotnet-cli-build/Crossgen.cs b/build_projects/dotnet-cli-build/Crossgen.cs index 48ebdebfa..8c4d4954e 100644 --- a/build_projects/dotnet-cli-build/Crossgen.cs +++ b/build_projects/dotnet-cli-build/Crossgen.cs @@ -82,7 +82,38 @@ namespace Microsoft.DotNet.Build.Tasks protected override MessageImportance StandardOutputLoggingImportance { - get { return MessageImportance.High; } // or else the output doesn't get logged by default + // Default is low, but we want to see output at normal verbosity. + get { return MessageImportance.Normal; } + } + + protected override MessageImportance StandardErrorLoggingImportance + { + // This turns stderr messages into msbuild errors below. + get { return MessageImportance.High; } + } + + protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) + { + // Crossgen's error/warning formatting is inconsistent and so we do + // not use the "canonical error format" handling of base. + // + // Furthermore, we don't want to log crossgen warnings as msbuild + // warnings because we cannot prevent them and they are only + // occasionally formatted as something that base would recognize as + // a canonically formatted warning anyway. + // + // One thing that is consistent is that crossgne errors go to stderr + // and everything else goes to stdout. Above, we set stderr to high + // importance above, and stdout to normal. So we can use that here + // to distinguish between errors and messages. + if (messageImportance == MessageImportance.High) + { + Log.LogError(singleLine); + } + else + { + Log.LogMessage(messageImportance, singleLine); + } } protected override string GenerateFullPathToTool() diff --git a/build_projects/update-dependencies/Program.cs b/build_projects/update-dependencies/Program.cs index 26e353abb..8f3aa30da 100644 --- a/build_projects/update-dependencies/Program.cs +++ b/build_projects/update-dependencies/Program.cs @@ -87,7 +87,14 @@ namespace Microsoft.DotNet.Scripts if (s_config.HasVersionFragment("aspnet")) { - yield return CreateRegexUpdater(dependencyVersionsPath, "MicrosoftAspNetCoreAppPackageVersion", "Microsoft.AspNetCore.App"); + yield return CreateRegexUpdater(dependencyVersionsPath, "MicrosoftAspNetCoreAllPackageVersion", "Microsoft.AspNetCore.All"); + yield return CreateRegexUpdater(dependencyVersionsPath, "DotnetEfPackageVersion", "dotnet-ef"); + yield return CreateRegexUpdater(dependencyVersionsPath, "MicrosoftAspNetCoreDeveloperCertificatesXPlatPackageVersion", "Microsoft.AspNetCore.DeveloperCertificates.XPlat"); + yield return CreateRegexUpdater(dependencyVersionsPath, "MicrosoftAspNetCoreMvcPackageVersion", "Microsoft.AspNetCore.Mvc"); + yield return CreateRegexUpdater(dependencyVersionsPath, "DotnetDevCertsPackageVersion", "dotnet-dev-certs"); + yield return CreateRegexUpdater(dependencyVersionsPath, "DotnetSqlCachePackageVersion", "dotnet-sql-cache"); + yield return CreateRegexUpdater(dependencyVersionsPath, "DotnetUserSecretsPackageVersion", "dotnet-user-secrets"); + yield return CreateRegexUpdater(dependencyVersionsPath, "DotnetWatchPackageVersion", "dotnet-watch"); } if (s_config.HasVersionFragment("coresetup")) { diff --git a/packaging/windows/clisdk/bundle.wxl b/packaging/windows/clisdk/bundle.wxl index 18fc7750c..dee03bb18 100644 --- a/packaging/windows/clisdk/bundle.wxl +++ b/packaging/windows/clisdk/bundle.wxl @@ -74,6 +74,6 @@ Resources Installation note A command will be run during the install process that will improve project restore speed and enable offline access. It will take up to a minute to complete. - If you plan to use .NET Core 2.1 with Visual Studio, Visual Studio 2017 15.7 or newer is recommended. <A HREF="https://go.microsoft.com/fwlink/?linkid=866799">Learn More</A>. + If you plan to use .NET Core 2.2 with Visual Studio, Visual Studio 2017 15.9 or newer is recommended. <A HREF="https://go.microsoft.com/fwlink/?linkid=866799">Learn More</A>. diff --git a/packaging/windows/clisdk/bundle.wxs b/packaging/windows/clisdk/bundle.wxs index 2e9304ce5..af6482f3f 100644 --- a/packaging/windows/clisdk/bundle.wxs +++ b/packaging/windows/clisdk/bundle.wxs @@ -7,7 +7,7 @@ diff --git a/run-build.ps1 b/run-build.ps1 index 07fbb03f9..8e54ae652 100644 --- a/run-build.ps1 +++ b/run-build.ps1 @@ -76,6 +76,9 @@ $env:DOTNET_MULTILEVEL_LOOKUP=0 # Turn off MSBuild Node re-use $env:MSBUILDDISABLENODEREUSE=1 +# Workaround for the sockets issue when restoring with many nuget feeds. +$env:DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 + # Enable vs test console logging $env:VSTEST_BUILD_TRACE=1 $env:VSTEST_TRACE_BUILD=1 @@ -109,7 +112,7 @@ if ($NoBuild) } else { - dotnet msbuild build.proj /p:Architecture=$Architecture /p:GeneratePropsFile=true /t:WriteDynamicPropsToStaticPropsFiles $ExtraParametersNoTargets - dotnet msbuild build.proj /m /v:normal /fl /flp:v=diag /bl /p:Architecture=$Architecture $ExtraParameters + dotnet msbuild build.proj /bl:msbuild.generatepropsfile.binlog /p:Architecture=$Architecture /p:GeneratePropsFile=true /t:WriteDynamicPropsToStaticPropsFiles $ExtraParametersNoTargets + dotnet msbuild build.proj /bl:msbuild.generatepropsfile.binlog /m /v:normal /fl /flp:v=diag /bl /p:Architecture=$Architecture $ExtraParameters if($LASTEXITCODE -ne 0) { throw "Failed to build" } } diff --git a/run-build.sh b/run-build.sh index 756b46b87..b58ec14e9 100755 --- a/run-build.sh +++ b/run-build.sh @@ -156,6 +156,9 @@ export DOTNET_MULTILEVEL_LOOKUP=0 # Turn off MSBuild Node re-use export MSBUILDDISABLENODEREUSE=1 +# Workaround for the sockets issue when restoring with many nuget feeds. +export DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 + # Install a stage 0 INSTALL_ARCHITECTURE=$ARCHITECTURE archlower="$(echo $ARCHITECTURE | awk '{print tolower($0)}')" @@ -191,8 +194,8 @@ fi export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 if [ $BUILD -eq 1 ]; then - dotnet msbuild build.proj /p:Architecture=$ARCHITECTURE $CUSTOM_BUILD_ARGS /p:GeneratePropsFile=true /t:WriteDynamicPropsToStaticPropsFiles ${argsnotargets[@]} - dotnet msbuild build.proj /m /v:normal /fl /flp:v=diag /bl /p:Architecture=$ARCHITECTURE $CUSTOM_BUILD_ARGS $args + dotnet msbuild build.proj /bl:msbuild.generatepropsfile.binlog /p:Architecture=$ARCHITECTURE $CUSTOM_BUILD_ARGS /p:GeneratePropsFile=true /t:WriteDynamicPropsToStaticPropsFiles ${argsnotargets[@]} + dotnet msbuild build.proj /bl:msbuild.mainbuild.binlog /m /v:normal /fl /flp:v=diag /bl /p:Architecture=$ARCHITECTURE $CUSTOM_BUILD_ARGS $args else echo "Not building due to --nobuild" echo "Command that would be run is: 'dotnet msbuild build.proj /m /p:Architecture=$ARCHITECTURE $CUSTOM_BUILD_ARGS $args'" diff --git a/scripts/docker/debian/Dockerfile b/scripts/docker/debian/Dockerfile index d54afeb0e..4b5515ec0 100644 --- a/scripts/docker/debian/Dockerfile +++ b/scripts/docker/debian/Dockerfile @@ -4,47 +4,16 @@ # # Dockerfile that creates a container suitable to build dotnet-cli -FROM debian:jessie +FROM microsoft/dotnet-buildtools-prereqs:debian-8.2-debpkg-d770b8b-20180628122423 # Misc Dependencies for build -RUN apt-get update && \ +RUN rm -rf /var/lib/apt/lists/* && \ + apt-get update && \ apt-get -qqy install \ - curl \ - unzip \ - gettext \ sudo && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -# This could become a "microsoft/coreclr" image, since it just installs the dependencies for CoreCLR (and stdlib) -RUN apt-get update &&\ - apt-get -qqy install \ - libunwind8 \ - libkrb5-3 \ - libicu52 \ - liblttng-ust0 \ - libssl1.0.0 \ - zlib1g \ - libuuid1 && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Install Build Prereqs -RUN apt-get update && \ - apt-get -qqy install \ - debhelper \ - build-essential \ - devscripts \ - git \ - cmake \ - clang-3.5 && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Use clang as c++ compiler -RUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-3.5 100 -RUN update-alternatives --set c++ /usr/bin/clang++-3.5 - # Setup User to match Host User, and give superuser permissions ARG USER_ID=0 RUN useradd -m code_executor -u ${USER_ID} -g sudo diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.cs b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.cs new file mode 100644 index 000000000..cda6dd3d0 --- /dev/null +++ b/src/Microsoft.DotNet.MSBuildSdkResolver/Interop.cs @@ -0,0 +1,135 @@ +// 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.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.DotNet.MSBuildSdkResolver +{ + internal static partial class Interop + { + internal static readonly bool RunningOnWindows = +#if NET46 + // Not using RuntimeInformation on NET46 to avoid non-in-box framework API, + // which create deployment problems for the resolver. + Path.DirectorySeparatorChar == '\\'; +#else + RuntimeInformation.IsOSPlatform(OSPlatform.Windows); +#endif + + static Interop() + { + if (RunningOnWindows) + { + PreloadWindowsLibrary("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 PreloadWindowsLibrary(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.dll", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] + private static extern IntPtr LoadLibraryExW(string lpFileName, IntPtr hFile, int dwFlags); + + + [Flags] + internal enum hostfxr_resolve_sdk2_flags_t : int + { + disallow_prerelease = 0x1, + } + + internal enum hostfxr_resolve_sdk2_result_key_t : int + { + resolved_sdk_dir = 0, + global_json_path = 1, + } + + internal static class Windows + { + private const CharSet UTF16 = CharSet.Unicode; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = UTF16)] + internal delegate void hostfxr_resolve_sdk2_result_fn( + hostfxr_resolve_sdk2_result_key_t key, + string value); + + [DllImport("hostfxr", CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern int hostfxr_resolve_sdk2( + string exe_dir, + string working_dir, + hostfxr_resolve_sdk2_flags_t flags, + hostfxr_resolve_sdk2_result_fn result); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = UTF16)] + internal delegate void hostfxr_get_available_sdks_result_fn( + int sdk_count, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] + string[] sdk_dirs); + + [DllImport("hostfxr", CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern int hostfxr_get_available_sdks( + string exe_dir, + hostfxr_get_available_sdks_result_fn result); + } + + internal static class Unix + { + // Ansi marhsaling on Unix is actually UTF8 + private const CharSet UTF8 = CharSet.Ansi; + private static string PtrToStringUTF8(IntPtr ptr) => Marshal.PtrToStringAnsi(ptr); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = UTF8)] + internal delegate void hostfxr_resolve_sdk2_result_fn( + hostfxr_resolve_sdk2_result_key_t key, + string value); + + [DllImport("hostfxr", CharSet = UTF8, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern int hostfxr_resolve_sdk2( + string exe_dir, + string working_dir, + hostfxr_resolve_sdk2_flags_t flags, + hostfxr_resolve_sdk2_result_fn result); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = UTF8)] + internal delegate void hostfxr_get_available_sdks_result_fn( + int sdk_count, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] + string[] sdk_dirs); + + [DllImport("hostfxr", CharSet = UTF8, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + internal static extern int hostfxr_get_available_sdks( + string exe_dir, + hostfxr_get_available_sdks_result_fn result); + + [DllImport("libc", CharSet = UTF8, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr realpath(string path, IntPtr buffer); + + [DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + private static extern void free(IntPtr ptr); + + internal static string realpath(string path) + { + var ptr = realpath(path, IntPtr.Zero); + var result = PtrToStringUTF8(ptr); + free(ptr); + return result; + } + } + } +} diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/NETCoreSdkResolver.cs b/src/Microsoft.DotNet.MSBuildSdkResolver/NETCoreSdkResolver.cs new file mode 100644 index 000000000..e00c618d9 --- /dev/null +++ b/src/Microsoft.DotNet.MSBuildSdkResolver/NETCoreSdkResolver.cs @@ -0,0 +1,76 @@ +// 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; + +namespace Microsoft.DotNet.MSBuildSdkResolver +{ + internal static class NETCoreSdkResolver + { + public sealed class Result + { + /// + /// Path to .NET Core SDK selected by hostfxr (e.g. C:\Program Files\dotnet\sdk\2.1.300). + /// + public string ResolvedSdkDirectory; + + /// + /// Path to global.json file that impacted resolution. + /// + public string GlobalJsonPath; + + public void Initialize(Interop.hostfxr_resolve_sdk2_result_key_t key, string value) + { + switch (key) + { + case Interop.hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir: + ResolvedSdkDirectory = value; + break; + case Interop.hostfxr_resolve_sdk2_result_key_t.global_json_path: + GlobalJsonPath = value; + break; + } + } + } + + public static Result ResolveSdk( + string dotnetExeDirectory, + string globalJsonStartDirectory, + bool disallowPrerelease = false) + { + var result = new Result(); + var flags = disallowPrerelease ? Interop.hostfxr_resolve_sdk2_flags_t.disallow_prerelease : 0; + + int errorCode = Interop.RunningOnWindows + ? Interop.Windows.hostfxr_resolve_sdk2(dotnetExeDirectory, globalJsonStartDirectory, flags, result.Initialize) + : Interop.Unix.hostfxr_resolve_sdk2(dotnetExeDirectory, globalJsonStartDirectory, flags, result.Initialize); + + Debug.Assert((errorCode == 0) == (result.ResolvedSdkDirectory != null)); + return result; + } + + private sealed class SdkList + { + public string[] Entries; + + public void Initialize(int count, string[] entries) + { + entries = entries ?? Array.Empty(); + Debug.Assert(count == entries.Length); + Entries = entries; + } + } + + public static string[] GetAvailableSdks(string dotnetExeDirectory) + { + var list = new SdkList(); + + int errorCode = Interop.RunningOnWindows + ? Interop.Windows.hostfxr_get_available_sdks(dotnetExeDirectory, list.Initialize) + : Interop.Unix.hostfxr_get_available_sdks(dotnetExeDirectory, list.Initialize); + + return list.Entries; + } + } +} diff --git a/src/Microsoft.DotNet.MSBuildSdkResolver/VSSettings.cs b/src/Microsoft.DotNet.MSBuildSdkResolver/VSSettings.cs new file mode 100644 index 000000000..46dfda2e6 --- /dev/null +++ b/src/Microsoft.DotNet.MSBuildSdkResolver/VSSettings.cs @@ -0,0 +1,155 @@ +// 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.IO; +using System.Runtime.InteropServices; + +#if NET46 +using Microsoft.VisualStudio.Setup.Configuration; +#endif + +namespace Microsoft.DotNet.MSBuildSdkResolver +{ + internal sealed class VSSettings + { + private readonly object _lock = new object(); + private readonly string _settingsFilePath; + private readonly bool _disallowPrereleaseByDefault; + private FileInfo _settingsFile; + private bool _disallowPrerelease; + + // In the product, this singleton is used. It must be safe to use in parallel on multiple threads. + // In tests, mock instances can be created with the test constructor below. + public static readonly VSSettings Ambient = new VSSettings(); + + private VSSettings() + { +#if NET46 + if (!Interop.RunningOnWindows) + { + return; + } + + string instanceId; + string installationVersion; + bool isPrerelease; + + try + { + var configuration = new SetupConfiguration(); + var instance = configuration.GetInstanceForCurrentProcess(); + + instanceId = instance.GetInstanceId(); + installationVersion = instance.GetInstallationVersion(); + isPrerelease = ((ISetupInstanceCatalog)instance).IsPrerelease(); + } + catch (COMException) + { + return; + } + + var version = Version.Parse(installationVersion); + + _settingsFilePath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Microsoft", + "VisualStudio", + version.Major + ".0_" + instanceId, + "sdk.txt"); + + _disallowPrereleaseByDefault = !isPrerelease; + _disallowPrerelease = _disallowPrereleaseByDefault; +#endif + } + + // Test constructor + public VSSettings(string settingsFilePath, bool disallowPrereleaseByDefault) + { + _settingsFilePath = settingsFilePath; + _disallowPrereleaseByDefault = disallowPrereleaseByDefault; + _disallowPrerelease = _disallowPrereleaseByDefault; + } + + public bool DisallowPrerelease() + { + if (_settingsFilePath != null) + { + Refresh(); + } + + return _disallowPrerelease; + } + + private void Refresh() + { + Debug.Assert(_settingsFilePath != null); + + var file = new FileInfo(_settingsFilePath); + + // NB: All calls to Exists and LastWriteTimeUtc below will not hit the disk + // They will return data obtained during Refresh() here. + file.Refresh(); + + lock (_lock) + { + // File does not exist -> use default. + if (!file.Exists) + { + _disallowPrerelease = _disallowPrereleaseByDefault; + _settingsFile = file; + return; + } + + // File has not changed -> reuse prior read. + if (_settingsFile?.Exists == true && file.LastWriteTimeUtc <= _settingsFile.LastWriteTimeUtc) + { + return; + } + + // File has changed -> read from disk + // If we encounter an I/O exception, assume writer is in the process of updating file, + // ignore the exception, and use stale settings until the next resolution. + try + { + ReadFromDisk(); + _settingsFile = file; + return; + } + catch (IOException) { } + catch (UnauthorizedAccessException) { } + } + } + + private void ReadFromDisk() + { + using (var reader = new StreamReader(_settingsFilePath)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + int indexOfEquals = line.IndexOf('='); + if (indexOfEquals < 0 || indexOfEquals == (line.Length - 1)) + { + continue; + } + + string key = line.Substring(0, indexOfEquals).Trim(); + string value = line.Substring(indexOfEquals + 1).Trim(); + + if (key.Equals("UsePreviews", StringComparison.OrdinalIgnoreCase) + && bool.TryParse(value, out bool usePreviews)) + { + _disallowPrerelease = !usePreviews; + return; + } + } + } + + // File does not have UsePreviews entry -> use default + _disallowPrerelease = _disallowPrereleaseByDefault; + } + } +} + diff --git a/src/dotnet/ToolPackage/PackageLocation.cs b/src/dotnet/ToolPackage/PackageLocation.cs new file mode 100644 index 000000000..c0838d1b5 --- /dev/null +++ b/src/dotnet/ToolPackage/PackageLocation.cs @@ -0,0 +1,23 @@ + +using System; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ToolPackage +{ + internal class PackageLocation + { + public PackageLocation( + FilePath? nugetConfig = null, + DirectoryPath? rootConfigDirectory = null, + string[] additionalFeeds = null) + { + NugetConfig = nugetConfig; + RootConfigDirectory = rootConfigDirectory; + AdditionalFeeds = additionalFeeds ?? Array.Empty(); + } + + public FilePath? NugetConfig { get; } + public DirectoryPath? RootConfigDirectory { get; } + public string[] AdditionalFeeds { get; } + } +} diff --git a/src/dotnet/commands/dotnet-tool/ToolCommandRestorePassThroughOptions.cs b/src/dotnet/commands/dotnet-tool/ToolCommandRestorePassThroughOptions.cs new file mode 100644 index 000000000..511f8b867 --- /dev/null +++ b/src/dotnet/commands/dotnet-tool/ToolCommandRestorePassThroughOptions.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 Microsoft.DotNet.Cli.CommandLine; +using LocalizableStrings = Microsoft.DotNet.Tools.Restore.LocalizableStrings; + + +namespace Microsoft.DotNet.Cli +{ + internal static class ToolCommandRestorePassThroughOptions + { + public static Option DisableParallelOption() + { + return Create.Option( + "--disable-parallel", + LocalizableStrings.CmdDisableParallelOptionDescription, + Accept.NoArguments().ForwardAs("--disable-parallel")); + } + + public static Option NoCacheOption() + { + return Create.Option( + "--no-cache", + LocalizableStrings.CmdNoCacheOptionDescription, + Accept.NoArguments().ForwardAs("--no-cache")); + } + + public static Option IgnoreFailedSourcesOption() + { + return Create.Option( + "--ignore-failed-sources", + LocalizableStrings.CmdIgnoreFailedSourcesOptionDescription, + Accept.NoArguments().ForwardAs("--ignore-failed-sources")); + } + } +} diff --git a/test/EndToEnd/GivenAspNetAppsResolveImplicitVersions.cs b/test/EndToEnd/GivenAspNetAppsResolveImplicitVersions.cs index 9e3850e74..f8b781be6 100644 --- a/test/EndToEnd/GivenAspNetAppsResolveImplicitVersions.cs +++ b/test/EndToEnd/GivenAspNetAppsResolveImplicitVersions.cs @@ -52,7 +52,7 @@ namespace EndToEnd var bundledVersion = File.ReadAllText(bundledVersionPath).Trim(); restoredVersion.ToNormalizedString().Should().BeEquivalentTo(bundledVersion, - "The bundled aspnetcore versions set in Microsoft.NETCoreSdk.BundledVersions.props should be idenitical to the versions set in DependencyVersions.props." + + "The bundled aspnetcore versions set in Microsoft.NETCoreSdk.BundledVersions.props should be identical to the versions generated." + "Please update MSBuildExtensions.targets in this repo so these versions match."); } @@ -98,7 +98,7 @@ namespace EndToEnd var bundledVersion = File.ReadAllText(bundledVersionPath).Trim(); restoredVersion.ToNormalizedString().Should().BeEquivalentTo(bundledVersion, - "The bundled aspnetcore versions set in Microsoft.NETCoreSdk.BundledVersions.props should be idenitical to the versions set in DependencyVersions.props." + + "The bundled aspnetcore versions set in Microsoft.NETCoreSdk.BundledVersions.props should be identical to the versions set in DependencyVersions.props." + "Please update MSBuildExtensions.targets in this repo so these versions match."); } diff --git a/test/EndToEnd/SupportedNetCoreAppVersions.cs b/test/EndToEnd/SupportedNetCoreAppVersions.cs index fe1eb44e2..97e8262b9 100644 --- a/test/EndToEnd/SupportedNetCoreAppVersions.cs +++ b/test/EndToEnd/SupportedNetCoreAppVersions.cs @@ -18,7 +18,8 @@ namespace EndToEnd "1.0", "1.1", "2.0", - "2.1" + "2.1", + "2.2" }.Select(version => new object[] { version }); } } diff --git a/test/Microsoft.DotNet.TestFramework/TestAssetInstance.cs b/test/Microsoft.DotNet.TestFramework/TestAssetInstance.cs index 69a67a240..93509a21a 100644 --- a/test/Microsoft.DotNet.TestFramework/TestAssetInstance.cs +++ b/test/Microsoft.DotNet.TestFramework/TestAssetInstance.cs @@ -109,19 +109,23 @@ namespace Microsoft.DotNet.TestFramework return this; } - public TestAssetInstance WithNuGetConfig(string nugetCache) + public TestAssetInstance WithNuGetConfig(string nugetCache, string externalRestoreSources = null) { var thisAssembly = typeof(TestAssetInstance).GetTypeInfo().Assembly; var newNuGetConfig = Root.GetFile("NuGet.Config"); + externalRestoreSources = externalRestoreSources ?? string.Empty; var content = @" + $externalRestoreSources$ "; - content = content.Replace("$fullpath$", nugetCache); + content = content + .Replace("$fullpath$", nugetCache) + .Replace("$externalRestoreSources$", externalRestoreSources); using (var newNuGetConfigStream = new FileStream(newNuGetConfig.FullName, FileMode.Create, FileAccess.Write)) diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs index 7de277852..de2af4896 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs @@ -22,6 +22,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private string _stage2WithBackwardsCompatibleRuntimesDirectory; private string _testPackages; private string _testWorkingFolder; + private string _testArtifactsFolder; public static string RepoRoot { @@ -92,6 +93,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities public string Stage2WithBackwardsCompatibleRuntimesDirectory => _stage2WithBackwardsCompatibleRuntimesDirectory; public string TestPackages => _testPackages; public string TestWorkingFolder => _testWorkingFolder; + public string TestArtifactsFolder => _testArtifactsFolder; public RepoDirectoriesProvider( string artifacts = null, @@ -123,6 +125,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _testPackages = Path.Combine(_artifacts, "test", "packages"); } + _testArtifactsFolder = Path.Combine(_artifacts, "test", "artifacts"); + _testWorkingFolder = Path.Combine(RepoRoot, "bin", (previousStage + 1).ToString(), diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestAssetInstanceExtensions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestAssetInstanceExtensions.cs new file mode 100644 index 000000000..a1cf95916 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestAssetInstanceExtensions.cs @@ -0,0 +1,33 @@ +// 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.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.TestFramework; +using Microsoft.DotNet.Tools.Common; + +namespace Microsoft.DotNet.Tools.Test.Utilities +{ + public static class TestAssetInstanceExtensions + { + public static TestAssetInstance WithNuGetConfigAndExternalRestoreSources( + this TestAssetInstance testAssetInstance, string nugetCache) + { + var externalRestoreSourcesForTests = Path.Combine( + new RepoDirectoriesProvider().TestArtifactsFolder, "ExternalRestoreSourcesForTestsContainer.txt"); + var externalRestoreSources = File.Exists(externalRestoreSourcesForTests) ? + File.ReadAllText(externalRestoreSourcesForTests) : + string.Empty; + + return testAssetInstance.WithNuGetConfig(nugetCache, externalRestoreSources); + } + } +} \ No newline at end of file