diff --git a/CliToolVersion.txt b/CliToolVersion.txt new file mode 100644 index 0000000000..6b51205059 --- /dev/null +++ b/CliToolVersion.txt @@ -0,0 +1 @@ +1.0.0-preview3-003981 \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index f780c43a82..441eef967c 100644 --- a/build.ps1 +++ b/build.ps1 @@ -33,7 +33,7 @@ cd $PSScriptRoot $repoFolder = $PSScriptRoot $env:REPO_FOLDER = $repoFolder -$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip" +$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" if ($env:KOREBUILD_ZIP) { $koreBuildZip=$env:KOREBUILD_ZIP @@ -64,4 +64,10 @@ if (!(Test-Path $buildFolder)) { } } +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +$dotnetHome = "$buildFolder\.dotnet" +mkdir $dotnetHome -ErrorAction Ignore | Out-Null +& .\dotnet-install.ps1 -Version $(Get-Content .\CliToolVersion.txt) -InstallDir $dotnetHome +& .\dotnet-install.ps1 -Version 1.1.0 -Channel release/1.1.0 -SharedRuntime -InstallDir $dotnetHome + &"$buildFile" $args \ No newline at end of file diff --git a/build.sh b/build.sh index 0a1463b9e4..cdfdace2ba 100755 --- a/build.sh +++ b/build.sh @@ -2,7 +2,7 @@ repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $repoFolder -koreBuildZip="https://github.com/aspnet/KoreBuild/archive/feature/msbuild.zip" +koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" if [ ! -z $KOREBUILD_ZIP ]; then koreBuildZip=$KOREBUILD_ZIP fi @@ -43,4 +43,10 @@ if test ! -d $buildFolder; then fi fi +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +dotnetHome="$buildFolder/.dotnet" +mkdir -p $dotnetHome +./dotnet-install.sh --install-dir $dotnetHome --version $(cat CliToolVersion.txt) +./dotnet-install.sh --install-dir $dotnetHome --version 1.1.0 --channel release/1.1.0 --shared-runtime + $buildFile -r $repoFolder "$@" \ No newline at end of file diff --git a/dotnet-install.ps1 b/dotnet-install.ps1 new file mode 100644 index 0000000000..d656007d5f --- /dev/null +++ b/dotnet-install.ps1 @@ -0,0 +1,427 @@ +# +# 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. +# + +<# +.SYNOPSIS + Installs dotnet cli +.DESCRIPTION + Installs dotnet cli. If dotnet installation already exists in the given directory + it will update it only if the requested version differs from the one already installed. +.PARAMETER Channel + Default: preview + Channel is the way of reasoning about stability and quality of dotnet. This parameter takes one of the values: + - future - Possibly unstable, frequently changing, may contain new finished and unfinished features + - preview - Pre-release stable with known issues and feature gaps + - production - Most stable releases +.PARAMETER Version + Default: latest + Represents a build version on specific channel. Possible values: + - 4-part version in a format A.B.C.D - represents specific version of build + - latest - most latest build on specific channel + - lkg - last known good version on specific channel + Note: LKG work is in progress. Once the work is finished, this will become new default +.PARAMETER InstallDir + Default: %LocalAppData%\Microsoft\dotnet + Path to where to install dotnet. Note that binaries will be placed directly in a given directory. +.PARAMETER Architecture + Default: - this value represents currently running OS architecture + Architecture of dotnet binaries to be installed. + Possible values are: , x64 and x86 +.PARAMETER SharedRuntime + Default: false + Installs just the shared runtime bits, not the entire SDK +.PARAMETER DebugSymbols + If set the installer will include symbols in the installation. +.PARAMETER DryRun + If set it will not perform installation but instead display what command line to use to consistently install + currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link + with specific version so that this command can be used deterministicly in a build script. + It also displays binaries location if you prefer to install or download it yourself. +.PARAMETER NoPath + By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder. + If set it will display binaries location but not set any environment variable. +.PARAMETER Verbose + Displays diagnostics information. +.PARAMETER AzureFeed + Default: https://dotnetcli.azureedge.net/dotnet + This parameter should not be usually changed by user. It allows to change URL for the Azure feed used by this installer. +.PARAMETER ProxyAddress + If set, the installer will use the proxy when making web requests +#> +[cmdletbinding()] +param( + [string]$Channel="rel-1.0.0", + [string]$Version="Latest", + [string]$InstallDir="", + [string]$Architecture="", + [switch]$SharedRuntime, + [switch]$DebugSymbols, # TODO: Switch does not work yet. Symbols zip is not being uploaded yet. + [switch]$DryRun, + [switch]$NoPath, + [string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet", + [string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet", + [string]$ProxyAddress +) + +Set-StrictMode -Version Latest +$ErrorActionPreference="Stop" +$ProgressPreference="SilentlyContinue" + +$BinFolderRelativePath="" + +# example path with regex: shared/1.0.0-beta-12345/somepath +$VersionRegEx="/\d+\.\d+[^/]+/" +$OverrideNonVersionedFiles=$true + +function Say($str) { + Write-Host "dotnet-install: $str" +} + +function Say-Verbose($str) { + Write-Verbose "dotnet-install: $str" +} + +function Say-Invocation($Invocation) { + $command = $Invocation.MyCommand; + $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") + Say-Verbose "$command $args" +} + +function Get-Machine-Architecture() { + Say-Invocation $MyInvocation + + # possible values: AMD64, IA64, x86 + return $ENV:PROCESSOR_ARCHITECTURE +} + +# TODO: Architecture and CLIArchitecture should be unified +function Get-CLIArchitecture-From-Architecture([string]$Architecture) { + Say-Invocation $MyInvocation + + switch ($Architecture.ToLower()) { + { $_ -eq "" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } + { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } + { $_ -eq "x86" } { return "x86" } + default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" } + } +} + +function Get-Version-Info-From-Version-Text([string]$VersionText) { + Say-Invocation $MyInvocation + + $Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries)); + + $VersionInfo = @{} + $VersionInfo.CommitHash = $Data[0].Trim() + $VersionInfo.Version = $Data[1].Trim() + return $VersionInfo +} + +function Load-Assembly([string] $Assembly) { + try { + Add-Type -Assembly $Assembly | Out-Null + } + catch { + # On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd. + # Loading the base class assemblies is not unnecessary as the types will automatically get resolved. + } +} + +function GetHTTPResponse([Uri] $Uri) +{ + $HttpClient = $null + + try { + # HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet. + Load-Assembly -Assembly System.Net.Http + if($ProxyAddress){ + $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler + $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress} + $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler + } + else { + $HttpClient = New-Object System.Net.Http.HttpClient + } + + $Response = $HttpClient.GetAsync($Uri).Result + if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) + { + $ErrorMsg = "Failed to download $Uri." + if ($Response -ne $null) + { + $ErrorMsg += " $Response" + } + + throw $ErrorMsg + } + + return $Response + } + finally { + if ($HttpClient -ne $null) { + $HttpClient.Dispose() + } + } +} + + +function Get-Latest-Version-Info([string]$AzureFeed, [string]$AzureChannel, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + $VersionFileUrl = $null + if ($SharedRuntime) { + $VersionFileUrl = "$UncachedFeed/$AzureChannel/dnvm/latest.sharedfx.win.$CLIArchitecture.version" + } + else { + $VersionFileUrl = "$UncachedFeed/Sdk/$AzureChannel/latest.version" + } + + $Response = GetHTTPResponse -Uri $VersionFileUrl + $StringContent = $Response.Content.ReadAsStringAsync().Result + + switch ($Response.Content.Headers.ContentType) { + { ($_ -eq "application/octet-stream") } { $VersionText = [Text.Encoding]::UTF8.GetString($StringContent) } + { ($_ -eq "text/plain") } { $VersionText = $StringContent } + default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } + } + + $VersionInfo = Get-Version-Info-From-Version-Text $VersionText + + return $VersionInfo +} + +# TODO: AzureChannel and Channel should be unified +function Get-Azure-Channel-From-Channel([string]$Channel) { + Say-Invocation $MyInvocation + + # For compatibility with build scripts accept also directly Azure channels names + switch ($Channel.ToLower()) { + { ($_ -eq "future") -or ($_ -eq "dev") } { return "dev" } + { $_ -eq "production" } { throw "Production channel does not exist yet" } + default { return $_ } + } +} + +function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$AzureChannel, [string]$CLIArchitecture, [string]$Version) { + Say-Invocation $MyInvocation + + switch ($Version.ToLower()) { + { $_ -eq "latest" } { + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -AzureChannel $AzureChannel -CLIArchitecture $CLIArchitecture + return $LatestVersionInfo.Version + } + { $_ -eq "lkg" } { throw "``-Version LKG`` not supported yet." } + default { return $Version } + } +} + +function Get-Download-Links([string]$AzureFeed, [string]$AzureChannel, [string]$SpecificVersion, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + $ret = @() + + if ($SharedRuntime) { + $PayloadURL = "$AzureFeed/$AzureChannel/Binaries/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip" + } + else { + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" + } + + Say-Verbose "Constructed payload URL: $PayloadURL" + $ret += $PayloadURL + + return $ret +} + +function Get-User-Share-Path() { + Say-Invocation $MyInvocation + + $InstallRoot = $env:DOTNET_INSTALL_DIR + if (!$InstallRoot) { + $InstallRoot = "$env:LocalAppData\Microsoft\dotnet" + } + return $InstallRoot +} + +function Resolve-Installation-Path([string]$InstallDir) { + Say-Invocation $MyInvocation + + if ($InstallDir -eq "") { + return Get-User-Share-Path + } + return $InstallDir +} + +function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) { + Say-Invocation $MyInvocation + + $VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile + Say-Verbose "Local version file: $VersionFile" + + if (Test-Path $VersionFile) { + $VersionText = cat $VersionFile + Say-Verbose "Local version file text: $VersionText" + return Get-Version-Info-From-Version-Text $VersionText + } + + Say-Verbose "Local version file not found." + + return $null +} + +function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) { + Say-Invocation $MyInvocation + + $DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion + Say-Verbose "Is-Dotnet-Package-Installed: Path to a package: $DotnetPackagePath" + return Test-Path $DotnetPackagePath -PathType Container +} + +function Get-Absolute-Path([string]$RelativeOrAbsolutePath) { + # Too much spam + # Say-Invocation $MyInvocation + + return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath) +} + +function Get-Path-Prefix-With-Version($path) { + $match = [regex]::match($path, $VersionRegEx) + if ($match.Success) { + return $entry.FullName.Substring(0, $match.Index + $match.Length) + } + + return $null +} + +function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) { + Say-Invocation $MyInvocation + + $ret = @() + foreach ($entry in $Zip.Entries) { + $dir = Get-Path-Prefix-With-Version $entry.FullName + if ($dir -ne $null) { + $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) + if (-Not (Test-Path $path -PathType Container)) { + $ret += $dir + } + } + } + + $ret = $ret | Sort-Object | Get-Unique + + $values = ($ret | foreach { "$_" }) -join ";" + Say-Verbose "Directories to unpack: $values" + + return $ret +} + +# Example zip content and extraction algorithm: +# Rule: files if extracted are always being extracted to the same relative path locally +# .\ +# a.exe # file does not exist locally, extract +# b.dll # file exists locally, override only if $OverrideFiles set +# aaa\ # same rules as for files +# ... +# abc\1.0.0\ # directory contains version and exists locally +# ... # do not extract content under versioned part +# abc\asd\ # same rules as for files +# ... +# def\ghi\1.0.1\ # directory contains version and does not exist locally +# ... # extract content +function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { + Say-Invocation $MyInvocation + + Load-Assembly -Assembly System.IO.Compression.FileSystem + Set-Variable -Name Zip + try { + $Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) + + $DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath + + foreach ($entry in $Zip.Entries) { + $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName + if (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { + $DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName) + $DestinationDir = Split-Path -Parent $DestinationPath + $OverrideFiles=$OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath)) + if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) { + New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles) + } + } + } + } + finally { + if ($Zip -ne $null) { + $Zip.Dispose() + } + } +} + +function DownloadFile([Uri]$Uri, [string]$OutPath) { + $Stream = $null + + try { + $Response = GetHTTPResponse -Uri $Uri + $Stream = $Response.Content.ReadAsStreamAsync().Result + $File = [System.IO.File]::Create($OutPath) + $Stream.CopyTo($File) + $File.Close() + } + finally { + if ($Stream -ne $null) { + $Stream.Dispose() + } + } +} + +$AzureChannel = Get-Azure-Channel-From-Channel -Channel $Channel +$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture +$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -AzureChannel $AzureChannel -CLIArchitecture $CLIArchitecture -Version $Version +$DownloadLinks = Get-Download-Links -AzureFeed $AzureFeed -AzureChannel $AzureChannel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture + +if ($DryRun) { + Say "Payload URLs:" + foreach ($DownloadLink in $DownloadLinks) { + Say "- $DownloadLink" + } + Say "Repeatable invocation: .\$($MyInvocation.MyCommand) -Version $SpecificVersion -Channel $Channel -Architecture $CLIArchitecture -InstallDir $InstallDir" + exit 0 +} + +$InstallRoot = Resolve-Installation-Path $InstallDir +Say-Verbose "InstallRoot: $InstallRoot" + +$IsSdkInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage "sdk" -SpecificVersion $SpecificVersion +Say-Verbose ".NET SDK installed? $IsSdkInstalled" +if ($IsSdkInstalled) { + Say ".NET SDK version $SpecificVersion is already installed." + exit 0 +} + +New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null + +foreach ($DownloadLink in $DownloadLinks) { + $ZipPath = [System.IO.Path]::GetTempFileName() + Say "Downloading $DownloadLink" + DownloadFile -Uri $DownloadLink -OutPath $ZipPath + + Say "Extracting zip from $DownloadLink" + Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot + + Remove-Item $ZipPath +} + +$BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath) +if (-Not $NoPath) { + Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." + $env:path = "$BinPath;" + $env:path +} +else { + Say "Binaries of dotnet can be found in $BinPath" +} + +Say "Installation finished" +exit 0 diff --git a/dotnet-install.sh b/dotnet-install.sh new file mode 100755 index 0000000000..7f6f869564 --- /dev/null +++ b/dotnet-install.sh @@ -0,0 +1,688 @@ +# 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: This script should be compatible with the dash shell used in Ubuntu. So avoid bashisms! See https://wiki.ubuntu.com/DashAsBinSh for more info + +# Stop script on NZEC +set -e +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u +# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success +# This is causing it to fail +set -o pipefail + +# Use in the the functions: eval $invocation +invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' + +# standard output may be used as a return value in the functions +# we need a way to write text on the screen in the functions so that +# it won't interfere with the return value. +# Exposing stream 3 as a pipe to standard output of the script itself +exec 3>&1 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ]; then + # see if it supports colors + ncolors=$(tput colors) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_err() { + printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +get_current_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + + case "$ID.$VERSION_ID" in + "centos.7") + echo "centos" + return 0 + ;; + "debian.8") + echo "debian" + return 0 + ;; + "fedora.23") + echo "fedora.23" + return 0 + ;; + "fedora.24") + echo "fedora.24" + return 0 + ;; + "opensuse.13.2") + echo "opensuse.13.2" + return 0 + ;; + "opensuse.42.1") + echo "opensuse.42.1" + return 0 + ;; + "rhel.7.0" | "rhel.7.1" | "rhel.7.2") + echo "rhel" + return 0 + ;; + "ubuntu.14.04") + echo "ubuntu" + return 0 + ;; + "ubuntu.16.04") + echo "ubuntu.16.04" + return 0 + ;; + "ubuntu.16.10") + echo "ubuntu.16.10" + return 0 + ;; + "alpine.3.4.3") + echo "alpine" + return 0 + ;; + esac + fi + fi + + say_err "OS name could not be detected: $ID.$VERSION_ID" + return 1 +} + +machine_has() { + eval $invocation + + which "$1" > /dev/null 2>&1 + return $? +} + +check_min_reqs() { + if ! machine_has "curl"; then + say_err "curl is required to download dotnet. Install curl to proceed." + return 1 + fi + + return 0 +} + +check_pre_reqs() { + eval $invocation + + local failing=false; + + if [ "${DOTNET_INSTALL_SKIP_PREREQS:-}" = "1" ]; then + return 0 + fi + + if [ "$(uname)" = "Linux" ]; then + if ! [ -x "$(command -v ldconfig)" ]; then + echo "ldconfig is not in PATH, trying /sbin/ldconfig." + LDCONFIG_COMMAND="/sbin/ldconfig" + else + LDCONFIG_COMMAND="ldconfig" + fi + + [ -z "$($LDCONFIG_COMMAND -p | grep libunwind)" ] && say_err "Unable to locate libunwind. Install libunwind to continue" && failing=true + [ -z "$($LDCONFIG_COMMAND -p | grep libssl)" ] && say_err "Unable to locate libssl. Install libssl to continue" && failing=true + [ -z "$($LDCONFIG_COMMAND -p | grep libcurl)" ] && say_err "Unable to locate libcurl. Install libcurl to continue" && failing=true + [ -z "$($LDCONFIG_COMMAND -p | grep libicu)" ] && say_err "Unable to locate libicu. Install libicu to continue" && failing=true + fi + + if [ "$failing" = true ]; then + return 1 + fi + + return 0 +} + +# args: +# input - $1 +to_lowercase() { + #eval $invocation + + echo "$1" | tr '[:upper:]' '[:lower:]' + return 0 +} + +# args: +# input - $1 +remove_trailing_slash() { + #eval $invocation + + local input=${1:-} + echo "${input%/}" + return 0 +} + +# args: +# input - $1 +remove_beginning_slash() { + #eval $invocation + + local input=${1:-} + echo "${input#/}" + return 0 +} + +# args: +# root_path - $1 +# child_path - $2 - this parameter can be empty +combine_paths() { + eval $invocation + + # TODO: Consider making it work with any number of paths. For now: + if [ ! -z "${3:-}" ]; then + say_err "combine_paths: Function takes two parameters." + return 1 + fi + + local root_path=$(remove_trailing_slash $1) + local child_path=$(remove_beginning_slash ${2:-}) + say_verbose "combine_paths: root_path=$root_path" + say_verbose "combine_paths: child_path=$child_path" + echo "$root_path/$child_path" + return 0 +} + +get_machine_architecture() { + eval $invocation + + # Currently the only one supported + echo "x64" + return 0 +} + +# args: +# architecture - $1 +get_normalized_architecture_from_architecture() { + eval $invocation + + local architecture=$(to_lowercase $1) + case $architecture in + \) + echo "$(get_normalized_architecture_from_architecture $(get_machine_architecture))" + return 0 + ;; + amd64|x64) + echo "x64" + return 0 + ;; + x86) + say_err "Architecture ``x86`` currently not supported" + return 1 + ;; + esac + + say_err "Architecture ``$architecture`` not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" + return 1 +} + +# version_info is a conceptual two line string representing commit hash and 4-part version +# format: +# Line 1: # commit_hash +# Line 2: # 4-part version + +# args: +# version_text - stdin +get_version_from_version_info() { + eval $invocation + + cat | tail -n 1 + return 0 +} + +# args: +# version_text - stdin +get_commit_hash_from_version_info() { + eval $invocation + + cat | head -n 1 + return 0 +} + +# args: +# install_root - $1 +# relative_path_to_package - $2 +# specific_version - $3 +is_dotnet_package_installed() { + eval $invocation + + local install_root=$1 + local relative_path_to_package=$2 + local specific_version=${3//[$'\t\r\n']} + + local dotnet_package_path=$(combine_paths $(combine_paths $install_root $relative_path_to_package) $specific_version) + say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" + + if [ -d "$dotnet_package_path" ]; then + return 0 + else + return 1 + fi +} + +# args: +# azure_feed - $1 +# azure_channel - $2 +# normalized_architecture - $3 +get_latest_version_info() { + eval $invocation + + local azure_feed=$1 + local azure_channel=$2 + local normalized_architecture=$3 + + local osname + osname=$(get_current_os_name) || return 1 + + local version_file_url=null + if [ "$shared_runtime" = true ]; then + version_file_url="$uncached_feed/$azure_channel/dnvm/latest.sharedfx.$osname.$normalized_architecture.version" + else + version_file_url="$uncached_feed/Sdk/$azure_channel/latest.version" + fi + say_verbose "get_latest_version_info: latest url: $version_file_url" + + download $version_file_url + return $? +} + +# args: +# channel - $1 +get_azure_channel_from_channel() { + eval $invocation + + local channel=$(to_lowercase $1) + case $channel in + future|dev) + echo "dev" + return 0 + ;; + production) + say_err "Production channel does not exist yet" + return 1 + esac + + echo $channel + return 0 +} + +# args: +# azure_feed - $1 +# azure_channel - $2 +# normalized_architecture - $3 +# version - $4 +get_specific_version_from_version() { + eval $invocation + + local azure_feed=$1 + local azure_channel=$2 + local normalized_architecture=$3 + local version=$(to_lowercase $4) + + case $version in + latest) + local version_info + version_info="$(get_latest_version_info $azure_feed $azure_channel $normalized_architecture)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_version_info + return 0 + ;; + lkg) + say_err "``--version LKG`` not supported yet." + return 1 + ;; + *) + echo $version + return 0 + ;; + esac +} + +# args: +# azure_feed - $1 +# azure_channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_download_link() { + eval $invocation + + local azure_feed=$1 + local azure_channel=$2 + local normalized_architecture=$3 + local specific_version=${4//[$'\t\r\n']} + + local osname + osname=$(get_current_os_name) || return 1 + + local download_link=null + if [ "$shared_runtime" = true ]; then + download_link="$azure_feed/$azure_channel/Binaries/$specific_version/dotnet-$osname-$normalized_architecture.$specific_version.tar.gz" + else + download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$osname-$normalized_architecture.$specific_version.tar.gz" + fi + + echo "$download_link" + return 0 +} + +get_user_share_path() { + eval $invocation + + if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then + echo $DOTNET_INSTALL_DIR + else + echo "$HOME/.dotnet" + fi + return 0 +} + +# args: +# install_dir - $1 +resolve_installation_path() { + eval $invocation + + local install_dir=$1 + if [ "$install_dir" = "" ]; then + local user_share_path=$(get_user_share_path) + say_verbose "resolve_installation_path: share_path=$user_share_path" + echo "$user_share_path" + return 0 + fi + + echo "$install_dir" + return 0 +} + +# args: +# install_root - $1 +get_installed_version_info() { + eval $invocation + + local install_root=$1 + local version_file=$(combine_paths "$install_root" "$local_version_file_relative_path") + say_verbose "Local version file: $version_file" + if [ ! -z "$version_file" ] | [ -r "$version_file" ]; then + local version_info="$(cat $version_file)" + echo "$version_info" + return 0 + fi + + say_verbose "Local version file not found." + return 0 +} + +# args: +# relative_or_absolute_path - $1 +get_absolute_path() { + eval $invocation + + local relative_or_absolute_path=$1 + echo $(cd $(dirname "$1") && pwd -P)/$(basename "$1") + return 0 +} + +# args: +# input_files - stdin +# root_path - $1 +# out_path - $2 +# override - $3 +copy_files_or_dirs_from_list() { + eval $invocation + + local root_path=$(remove_trailing_slash $1) + local out_path=$(remove_trailing_slash $2) + local override=$3 + local override_switch=$(if [ "$override" = false ]; then printf -- "-n"; fi) + + cat | uniq | while read -r file_path; do + local path=$(remove_beginning_slash ${file_path#$root_path}) + local target=$out_path/$path + if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then + mkdir -p $out_path/$(dirname $path) + cp -R $override_switch $root_path/$path $target + fi + done +} + +# args: +# zip_path - $1 +# out_path - $2 +extract_dotnet_package() { + eval $invocation + + local zip_path=$1 + local out_path=$2 + + local temp_out_path=$(mktemp -d $temporary_file_template) + + local failed=false + tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true + + local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' + find $temp_out_path -type f | grep -Eo $folders_with_version_regex | copy_files_or_dirs_from_list $temp_out_path $out_path false + find $temp_out_path -type f | grep -Ev $folders_with_version_regex | copy_files_or_dirs_from_list $temp_out_path $out_path true + + rm -rf $temp_out_path + + if [ "$failed" = true ]; then + say_err "Extraction failed" + return 1 + fi +} + +# args: +# remote_path - $1 +# [out_path] - $2 - stdout if not provided +download() { + eval $invocation + + local remote_path=$1 + local out_path=${2:-} + + local failed=false + if [ -z "$out_path" ]; then + curl --fail -s $remote_path || failed=true + else + curl --fail -s -o $out_path $remote_path || failed=true + fi + + if [ "$failed" = true ]; then + say_err "Download failed" + return 1 + fi +} + +calculate_vars() { + eval $invocation + + azure_channel=$(get_azure_channel_from_channel "$channel") + say_verbose "azure_channel=$azure_channel" + + normalized_architecture=$(get_normalized_architecture_from_architecture "$architecture") + say_verbose "normalized_architecture=$normalized_architecture" + + specific_version=$(get_specific_version_from_version $azure_feed $azure_channel $normalized_architecture $version) + say_verbose "specific_version=$specific_version" + if [ -z "$specific_version" ]; then + say_err "Could not get version information." + return 1 + fi + + download_link=$(construct_download_link $azure_feed $azure_channel $normalized_architecture $specific_version) + say_verbose "download_link=$download_link" + + install_root=$(resolve_installation_path $install_dir) + say_verbose "install_root=$install_root" +} + +install_dotnet() { + eval $invocation + + if is_dotnet_package_installed $install_root "sdk" $specific_version; then + say ".NET SDK version $specific_version is already installed." + return 0 + fi + + mkdir -p $install_root + zip_path=$(mktemp $temporary_file_template) + say_verbose "Zip path: $zip_path" + + say "Downloading $download_link" + download "$download_link" $zip_path + say_verbose "Downloaded file exists and readable? $(if [ -r $zip_path ]; then echo "yes"; else echo "no"; fi)" + + say "Extracting zip" + extract_dotnet_package $zip_path $install_root + + return 0 +} + +local_version_file_relative_path="/.version" +bin_folder_relative_path="" +temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" + +channel="rel-1.0.0" +version="Latest" +install_dir="" +architecture="" +debug_symbols=false +dry_run=false +no_path=false +azure_feed="https://dotnetcli.azureedge.net/dotnet" +uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet" +verbose=false +shared_runtime=false + +while [ $# -ne 0 ] +do + name=$1 + case $name in + -c|--channel|-[Cc]hannel) + shift + channel=$1 + ;; + -v|--version|-[Vv]ersion) + shift + version="$1" + ;; + -i|--install-dir|-[Ii]nstall[Dd]ir) + shift + install_dir="$1" + ;; + --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) + shift + architecture="$1" + ;; + --shared-runtime|-[Ss]hared[Rr]untime) + shared_runtime=true + ;; + --debug-symbols|-[Dd]ebug[Ss]ymbols) + debug_symbols=true + ;; + --dry-run|-[Dd]ry[Rr]un) + dry_run=true + ;; + --no-path|-[Nn]o[Pp]ath) + no_path=true + ;; + --verbose|-[Vv]erbose) + verbose=true + ;; + --azure-feed|-[Aa]zure[Ff]eed) + shift + azure_feed="$1" + ;; + -?|--?|-h|--help|-[Hh]elp) + script_name="$(basename $0)" + echo ".NET Tools Installer" + echo "Usage: $script_name [-c|--channel ] [-v|--version ] [-p|--prefix ]" + echo " $script_name -h|-?|--help" + echo "" + echo "$script_name is a simple command line interface for obtaining dotnet cli." + echo "" + echo "Options:" + echo " -c,--channel Download from the CHANNEL specified (default: $channel)." + echo " -Channel" + echo " -v,--version Use specific version, ``latest`` or ``lkg``. Defaults to ``latest``." + echo " -Version" + echo " -i,--install-dir Install under specified location (see Install Location below)" + echo " -InstallDir" + echo " --architecture Architecture of .NET Tools. Currently only x64 is supported." + echo " --arch,-Architecture,-Arch" + echo " --shared-runtime Installs just the shared runtime bits, not the entire SDK." + echo " -SharedRuntime" + echo " --debug-symbols,-DebugSymbols Specifies if symbols should be included in the installation." + echo " --dry-run,-DryRun Do not perform installation. Display download link." + echo " --no-path, -NoPath Do not set PATH for the current process." + echo " --verbose,-Verbose Display diagnostics information." + echo " --azure-feed,-AzureFeed Azure feed location. Defaults to $azure_feed" + echo " -?,--?,-h,--help,-Help Shows this help message" + echo "" + echo "Install Location:" + echo " Location is chosen in following order:" + echo " - --install-dir option" + echo " - Environmental variable DOTNET_INSTALL_DIR" + echo " - /usr/local/share/dotnet" + exit 0 + ;; + *) + say_err "Unknown argument \`$name\`" + exit 1 + ;; + esac + + shift +done + +check_min_reqs +calculate_vars +if [ "$dry_run" = true ]; then + say "Payload URL: $download_link" + say "Repeatable invocation: ./$(basename $0) --version $specific_version --channel $channel --install-dir $install_dir" + exit 0 +fi + +check_pre_reqs +install_dotnet + +bin_path=$(get_absolute_path $(combine_paths $install_root $bin_folder_relative_path)) +if [ "$no_path" = false ]; then + say "Adding to current process PATH: ``$bin_path``. Note: This change will be visible only when sourcing script." + export PATH=$bin_path:$PATH +else + say "Binaries of dotnet can be found in $bin_path" +fi + +say "Installation finished successfully." diff --git a/makefile.shade b/makefile.shade index 7ac55d8aa4..a20eb95f77 100644 --- a/makefile.shade +++ b/makefile.shade @@ -11,6 +11,25 @@ var AUTHORS='Microsoft Open Technologies, Inc.' use-standard-lifecycle k-standard-goals +#xunit-test + @{ + foreach (var project in Files.Include("test/*/project.json")) + { + var projectDir = Path.GetDirectoryName(project); + var projectName = Path.GetFileName(projectDir); + var targetDir = Path.Combine(projectDir, "bin", E("Configuration"), "netcoreapp1.0"); + var depsFile = Path.Combine(targetDir, projectName + ".deps.json"); + var configFile = Path.Combine(targetDir, projectName + ".runtimeconfig.json"); + var assembly = Path.Combine(targetDir, projectName + ".dll"); + var home = Environment.GetEnvironmentVariable("USERPROFILE") ?? Environment.GetEnvironmentVariable("HOME"); + var packageCache = Environment.GetEnvironmentVariable("NUGET_PACKAGES") ?? Path.Combine(home, ".nuget/packages"); + Exec(".build/.dotnet/dotnet", "exec --depsfile \"" + depsFile + "\"" + + " --runtimeconfig \"" + configFile + "\" " + + "\"" + Path.Combine(packageCache, "dotnet-test-xunit/2.2.0-preview2-build1029/lib/netcoreapp1.0/dotnet-test-xunit.dll") + "\" " + + "\"" + assembly+ "\" "); + } + } + #build-pack .build-compile target='compile' @{ if (Directory.Exists("src") && !IsLinux) diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs index 6428f7f6fe..9e17328c69 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs @@ -53,7 +53,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Scenario.AddTestProjectFolder(AppWithDeps); Scenario.AddTestProjectFolder(Dependency); - Scenario.Restore3(AppWithDeps); // restore3 should be transitive + Scenario.Restore(AppWithDeps); // restore3 should be transitive AppWithDepsFolder = Path.Combine(Scenario.WorkFolder, AppWithDeps); DependencyFolder = Path.Combine(Scenario.WorkFolder, Dependency); @@ -64,7 +64,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests // Wait for the process to start using (var wait = new WaitForFileToChange(StatusFile)) { - RunDotNetWatch(new[] { "run3", StatusFile }, Path.Combine(Scenario.WorkFolder, AppWithDeps)); + RunDotNetWatch(new[] { "run", StatusFile }, Path.Combine(Scenario.WorkFolder, AppWithDeps)); wait.Wait(_defaultTimeout, expectedToChange: true, diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs index 31b0cb9167..912b243115 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs @@ -200,7 +200,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests StartedFile = StatusFile + ".started"; Scenario.AddTestProjectFolder(TestAppName); - Scenario.Restore3(TestAppName); + Scenario.Restore(TestAppName); TestAppFolder = Path.Combine(Scenario.WorkFolder, TestAppName); } @@ -210,7 +210,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests // Wait for the process to start using (var wait = new WaitForFileToChange(StartedFile)) { - RunDotNetWatch(new[] { "run3", StatusFile }, Path.Combine(Scenario.WorkFolder, TestAppName)); + RunDotNetWatch(new[] { "run", StatusFile }, Path.Combine(Scenario.WorkFolder, TestAppName)); wait.Wait(_defaultTimeout, expectedToChange: true, diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs index 7c385a6ed1..81f5f962dc 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests // Wait for the process to start using (var wait = new WaitForFileToChange(scenario.StartedFile)) { - scenario.RunDotNetWatch(new[] { "run3", "-f", "netcoreapp1.0", scenario.StatusFile, "--no-exit" }); + scenario.RunDotNetWatch(new[] { "run", scenario.StatusFile, "--no-exit" }); wait.Wait(_defaultTimeout, expectedToChange: true, @@ -68,7 +68,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests // Wait for the process to start using (var wait = new WaitForFileToChange(scenario.StartedFile)) { - scenario.RunDotNetWatch(new[] { "run3", "-f", "netcoreapp1.0", scenario.StatusFile }); + scenario.RunDotNetWatch(new[] { "run", scenario.StatusFile }); wait.Wait(_defaultTimeout, expectedToChange: true, @@ -115,7 +115,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests StartedFile = StatusFile + ".started"; Scenario.AddTestProjectFolder(TestAppName); - Scenario.Restore3(TestAppName); + Scenario.Restore(TestAppName); TestAppFolder = Path.Combine(Scenario.WorkFolder, TestAppName); } diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs index 73eff54e4a..a8ce555257 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs @@ -57,41 +57,14 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } } - public void Restore(string project = null) - { - if (project == null) - { - project = WorkFolder; - } - else - { - project = Path.Combine(WorkFolder, project); - } - - _logger?.WriteLine($"Restoring project in {project}"); - - var restore = Command - .CreateDotNet("restore", new[] { project }) - .CaptureStdErr() - .CaptureStdOut() - .OnErrorLine(l => _logger?.WriteLine(l)) - .OnOutputLine(l => _logger?.WriteLine(l)) - .Execute(); - - if (restore.ExitCode != 0) - { - throw new Exception($"Exit code {restore.ExitCode}"); - } - } - - public void Restore3(string project) + public void Restore(string project) { project = Path.Combine(WorkFolder, project); _logger?.WriteLine($"Restoring msbuild project in {project}"); var restore = Command - .CreateDotNet("restore3", new [] { "/v:m" }) + .Create(new Muxer().MuxerPath, new[] { "restore", "/p:SkipInvalidConfigurations=true" }) .WorkingDirectory(project) .CaptureStdErr() .CaptureStdOut() diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj index f06027243d..9b081c4e68 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/AppWithDeps/AppWithDeps.csproj @@ -9,8 +9,8 @@ - - + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/Dependency/Dependency.csproj b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/Dependency/Dependency.csproj index 243360cf92..93df17cb59 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/Dependency/Dependency.csproj +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/Dependency/Dependency.csproj @@ -8,8 +8,8 @@ - - + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj index cb088af5e3..74c2998f65 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/GlobbingApp/GlobbingApp.csproj @@ -8,8 +8,8 @@ - - + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj index 67edbf1349..71c6a8d4ab 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/TestProjects/NoDepsApp/NoDepsApp.csproj @@ -8,8 +8,8 @@ - - + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json index ecaa2d4046..16ffa26808 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json @@ -10,7 +10,7 @@ } }, "dependencies": { - "dotnet-test-xunit": "2.2.0-*", + "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.AspNetCore.Testing": "1.1.0-*", "Microsoft.DotNet.Watcher.Tools": "1.0.0-*", "Microsoft.Extensions.Process.Sources": { diff --git a/test/Microsoft.DotNet.Watcher.Tools.Tests/Utilities/TemporaryCSharpProject.cs b/test/Microsoft.DotNet.Watcher.Tools.Tests/Utilities/TemporaryCSharpProject.cs index bad0e0a949..f73ba47f5b 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.Tests/Utilities/TemporaryCSharpProject.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.Tests/Utilities/TemporaryCSharpProject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; namespace Microsoft.DotNetWatcher.Tools.Tests @@ -28,14 +29,17 @@ namespace Microsoft.DotNetWatcher.Tools.Tests private readonly string _filename; private readonly TemporaryDirectory _directory; - private string[] _tfms; private List _items = new List(); + private List _properties = new List(); public TemporaryCSharpProject(string name, TemporaryDirectory directory) { Name = name; _filename = name + ".csproj"; _directory = directory; + + // workaround CLI issue + WithProperty("SkipInvalidConfigurations", "true"); } public string Name { get; } @@ -43,7 +47,28 @@ namespace Microsoft.DotNetWatcher.Tools.Tests public TemporaryCSharpProject WithTargetFrameworks(params string[] tfms) { - _tfms = tfms; + Debug.Assert(tfms.Length > 0); + var propertySpec = new PropertySpec + { + Value = string.Join(";", tfms) + }; + propertySpec.Name = tfms.Length == 1 + ? "TargetFramework" + : "TargetFrameworks"; + + return WithProperty(propertySpec); + } + + public TemporaryCSharpProject WithProperty(string name, string value) + => WithProperty(new PropertySpec { Name = name, Value = value }); + + public TemporaryCSharpProject WithProperty(PropertySpec property) + { + var sb = new StringBuilder(); + sb.Append('<').Append(property.Name).Append('>') + .Append(property.Value) + .Append("'); + _properties.Add(sb.ToString()); return this; } @@ -84,13 +109,7 @@ namespace Microsoft.DotNetWatcher.Tools.Tests public void Create() { - var tfm = _tfms == null || _tfms.Length == 0 - ? string.Empty - : _tfms.Length == 1 - ? $"{_tfms[0]}" - : $"{string.Join(";", _tfms)}"; - - _directory.CreateFile(_filename, string.Format(Template, tfm, string.Join("\r\n", _items))); + _directory.CreateFile(_filename, string.Format(Template, string.Join("\r\n", _properties), string.Join("\r\n", _items))); } public class ItemSpec @@ -102,5 +121,11 @@ namespace Microsoft.DotNetWatcher.Tools.Tests public bool Watch { get; set; } = true; public string Condition { get; set; } } + + public class PropertySpec + { + public string Name { get; set; } + public string Value { get; set; } + } } } \ No newline at end of file diff --git a/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json b/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json index f938c008bf..8aac1452b9 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json +++ b/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json @@ -5,7 +5,7 @@ "debugType": "portable" }, "dependencies": { - "dotnet-test-xunit": "2.2.0-*", + "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.DotNet.Watcher.Tools": "1.0.0-*", "xunit": "2.2.0-*" }, diff --git a/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json b/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json index 537709e5c8..7c6a5831f9 100644 --- a/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json +++ b/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json @@ -4,7 +4,7 @@ "keyFile": "../../tools/Key.snk" }, "dependencies": { - "dotnet-test-xunit": "2.2.0-*", + "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.Extensions.SecretManager.Tools": "1.0.0-*", "xunit": "2.2.0-*" },