Merge branch 'master' of https://github.com/dotnet/cli into centos-builds
Conflicts: scripts/compile.sh scripts/test/smoke-test.sh
This commit is contained in:
commit
3363707704
102 changed files with 3847 additions and 823 deletions
|
@ -18,6 +18,7 @@ In order to build .NET Command Line Interface, you need the following installed
|
|||
1. CMake (available from https://cmake.org/) is required to build the native host `corehost`. Make sure to add it to the PATH.
|
||||
2. git (available from http://www.git-scm.com/) on the PATH.
|
||||
3. clang (available from http://clang.llvm.org) on the PATH.
|
||||
|
||||
### For OS X
|
||||
|
||||
1. Xcode
|
||||
|
@ -37,7 +38,7 @@ In order to build .NET Command Line Interface, you need the following installed
|
|||
|
||||
##Adding a Command
|
||||
|
||||
The donet CLI considers any executable on the path named `dotnet-{commandName}` to be a command it can call out to. `dotnet publish`, for example, is added to the path as an executable called `dotnet-publish`. To add a new command we must create the executable and then add it to the distribution packages for installation.
|
||||
The dotnet CLI considers any executable on the path named `dotnet-{commandName}` to be a command it can call out to. `dotnet publish`, for example, is added to the path as an executable called `dotnet-publish`. To add a new command we must create the executable and then add it to the distribution packages for installation.
|
||||
|
||||
0. Create an issue on https://github.com/dotnet/cli and get consensus on the need for and behavior of the command.
|
||||
1. Add a new project for the command.
|
||||
|
@ -63,4 +64,4 @@ Each command's project root should contain a manpage-style Readme.md that descri
|
|||
|
||||
#### Add command to packages
|
||||
- Update the `symlinks` property of `packaging/debian/debian_config.json` to include the new command
|
||||
- Update the `$Projects` property in `packaging/osx/scripts/postinstall`
|
||||
- Update the `$Projects` property in `packaging/osx/scripts/postinstall`
|
||||
|
|
|
@ -61,6 +61,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0722D325
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MultiProjectValidator", "tools\MultiProjectValidator\MultiProjectValidator.xproj", "{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.DependencyModel", "src\Microsoft.Extensions.DependencyModel\Microsoft.Extensions.DependencyModel.xproj", "{688870C8-9843-4F9E-8576-D39290AD0F25}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -441,6 +443,22 @@ Global
|
|||
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|x64.Build.0 = Debug|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|x64.Build.0 = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -469,5 +487,6 @@ Global
|
|||
{7A75ACC4-3C2F-44E1-B492-0EC08704E9FF} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
|
||||
{BC765FBF-AD7A-4A99-9902-5540C5A74181} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
|
||||
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85} = {0722D325-24C8-4E83-B5AF-0A083E7F0749}
|
||||
{688870C8-9843-4F9E-8576-D39290AD0F25} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -59,18 +59,18 @@ Compiling to IL is done using:
|
|||
dotnet compile
|
||||
This will drop a binary in `./bin/[configuration]/[framework]/[binary name]` that you can just run.
|
||||
|
||||
Finally, you can also try out native compilation on Windows and Ubuntu and Mac.
|
||||
|
||||
**Note:** at this point, only the `helloworld` and `dotnetbot` samples will work with native compilation.
|
||||
Finally, you can also try out native compilation using RyuJIT as shown below:
|
||||
|
||||
dotnet compile --native
|
||||
|
||||
On Mac OSX, we currently support the C++ Codegenerator (as shown below) and support for RyuJIT (as exemplified above) is coming soon.
|
||||
The following command will perform native compilation using the C++ Codegenerator:
|
||||
|
||||
dotnet compile --native --cpp
|
||||
|
||||
This will drop a native single binary in `./bin/[configuration]/[framework]/native/[binary name]` that you can run.
|
||||
|
||||
**Note:** At this point, only the `helloworld` and `dotnetbot` samples will work with native compilation.
|
||||
|
||||
For more details, please refer to the [documentation](https://github.com/dotnet/corert/tree/master/Documentation).
|
||||
|
||||
Building from source
|
||||
|
|
10
build.sh
10
build.sh
|
@ -4,7 +4,8 @@
|
|||
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
#
|
||||
|
||||
# $1 is passed to package to enable deb or pkg packaging
|
||||
# Set OFFLINE environment variable to build offline
|
||||
|
||||
set -e
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
|
@ -27,12 +28,19 @@ do
|
|||
debug)
|
||||
export CONFIGURATION=Debug
|
||||
;;
|
||||
offline)
|
||||
export OFFLINE=true
|
||||
;;
|
||||
*)
|
||||
esac
|
||||
done
|
||||
|
||||
[ -z "$CONFIGURATION" ] && CONFIGURATION=Debug
|
||||
|
||||
if [ ! -z "$OFFLINE" ]; then
|
||||
header " - Offline Build - "
|
||||
fi
|
||||
|
||||
# Use a repo-local install directory (but not the artifacts directory because that gets cleaned a lot
|
||||
export DOTNET_INSTALL_DIR=$DIR/.dotnet_stage0/$RID
|
||||
[ -d $DOTNET_INSTALL_DIR ] || mkdir -p $DOTNET_INSTALL_DIR
|
||||
|
|
|
@ -6,11 +6,8 @@
|
|||
$Rid = "win7-x64"
|
||||
$Tfm = "dnxcore50"
|
||||
|
||||
$DnxVersion = "1.0.0-rc1-update1"
|
||||
|
||||
$RepoRoot = Convert-Path "$PSScriptRoot\.."
|
||||
$OutputDir = "$RepoRoot\artifacts\$Rid"
|
||||
$DnxDir = "$OutputDir\dnx"
|
||||
$Stage1Dir = "$OutputDir\stage1"
|
||||
$Stage2Dir = "$OutputDir\stage2"
|
||||
$HostDir = "$OutputDir\corehost"
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
#
|
||||
|
||||
param(
|
||||
[string]$Configuration="Debug")
|
||||
[string]$Configuration="Debug",
|
||||
[switch]$Offline)
|
||||
|
||||
. "$PSScriptRoot\_common.ps1"
|
||||
|
||||
|
@ -35,7 +36,11 @@ if (!$env:DOTNET_BUILD_VERSION) {
|
|||
}
|
||||
|
||||
Write-Host -ForegroundColor Green "*** Building dotnet tools version $($env:DOTNET_BUILD_VERSION) - $Configuration ***"
|
||||
& "$PSScriptRoot\compile.ps1" -Configuration:$Configuration
|
||||
if ($Offline)
|
||||
{
|
||||
Write-Host -ForegroundColor Yellow " - Offline Build -"
|
||||
}
|
||||
& "$PSScriptRoot\compile.ps1" -Configuration:$Configuration -Offline:$Offline
|
||||
if (!$?) {
|
||||
Write-Host "Building dotnet tools finished with errors."
|
||||
Exit 1
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
#
|
||||
|
||||
param([string]$Configuration = "Debug")
|
||||
param([string]$Configuration = "Debug",
|
||||
[switch]$Offline)
|
||||
|
||||
$ErrorActionPreference="Stop"
|
||||
|
||||
|
@ -13,6 +14,36 @@ $ErrorActionPreference="Stop"
|
|||
$StartPath = $env:PATH
|
||||
$StartDotNetHome = $env:DOTNET_HOME
|
||||
|
||||
function getDnx()
|
||||
{
|
||||
$DnxPackage = "dnx-coreclr-win-x64.1.0.0-rc1-update1.nupkg"
|
||||
$DnxVersion = "1.0.0-rc1-16231"
|
||||
$DnxDir = "$OutputDir\dnx"
|
||||
$DnxRoot = "$DnxDir/bin"
|
||||
|
||||
# check if the required dnx version is already downloaded
|
||||
if ((Test-Path "$DnxRoot\dnx.exe")) {
|
||||
$dnxOut = & "$DnxRoot\dnx.exe" --version
|
||||
|
||||
if ($dnxOut -Match $DnxVersion) {
|
||||
Write-Host "Dnx version - $DnxVersion already downloaded."
|
||||
return $DnxRoot
|
||||
}
|
||||
}
|
||||
|
||||
# Download dnx to copy to stage2
|
||||
Remove-Item -Recurse -Force -ErrorAction Ignore $DnxDir
|
||||
mkdir -Force "$DnxDir" | Out-Null
|
||||
|
||||
Write-Host "Downloading Dnx version - $DnxVersion."
|
||||
$DnxUrl="https://api.nuget.org/packages/$DnxPackage"
|
||||
Invoke-WebRequest -UseBasicParsing "$DnxUrl" -OutFile "$DnxDir\dnx.zip"
|
||||
|
||||
Add-Type -Assembly System.IO.Compression.FileSystem | Out-Null
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$DnxDir\dnx.zip", "$DnxDir")
|
||||
return $DnxRoot
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
# Check prereqs
|
||||
|
@ -23,40 +54,35 @@ Download it from https://www.cmake.org
|
|||
"@
|
||||
}
|
||||
|
||||
# Install a stage 0
|
||||
header "Installing dotnet stage 0"
|
||||
& "$PSScriptRoot\install.ps1"
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: $PSScriptRoot\install.ps1"
|
||||
Exit 1
|
||||
if($Offline){
|
||||
Write-Host "Skipping Stage 0, Dnx, and Packages dowlnoad: Offline build"
|
||||
}
|
||||
|
||||
# Put stage 0 on the path
|
||||
$DotNetTools = $env:DOTNET_INSTALL_DIR
|
||||
if (!$DotNetTools) {
|
||||
$DotNetTools = "$($env:LOCALAPPDATA)\Microsoft\dotnet"
|
||||
else {
|
||||
# Install a stage 0
|
||||
header "Installing dotnet stage 0"
|
||||
& "$PSScriptRoot\install.ps1"
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: $PSScriptRoot\install.ps1"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# Put stage 0 on the path
|
||||
$DotNetTools = $env:DOTNET_INSTALL_DIR
|
||||
if (!$DotNetTools) {
|
||||
$DotNetTools = "$($env:LOCALAPPDATA)\Microsoft\dotnet"
|
||||
}
|
||||
|
||||
$DnxRoot = getDnx
|
||||
|
||||
# Restore packages
|
||||
header "Restoring packages"
|
||||
& "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --no-cache
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: " "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --no-cache
|
||||
Exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Download dnx to copy to stage2
|
||||
if ((Test-Path "$DnxDir")) {
|
||||
Remove-Item -Recurse -Force $DnxDir
|
||||
}
|
||||
mkdir "$DnxDir" | Out-Null
|
||||
$DnxUrl="https://api.nuget.org/packages/dnx-coreclr-win-x64.$DnxVersion.nupkg"
|
||||
Invoke-WebRequest -UseBasicParsing "$DnxUrl" -OutFile "$DnxDir\dnx.zip"
|
||||
Add-Type -Assembly System.IO.Compression.FileSystem | Out-Null
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$DnxDir\dnx.zip", "$DnxDir")
|
||||
$DnxRoot = "$DnxDir/bin"
|
||||
|
||||
# Restore packages
|
||||
header "Restoring packages"
|
||||
& "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64" --no-cache
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: " dotnet restore "$RepoRoot" --quiet --runtime "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64" --no-cache
|
||||
Exit 1
|
||||
}
|
||||
|
||||
|
||||
header "Building corehost"
|
||||
pushd "$RepoRoot\src\corehost"
|
||||
try {
|
||||
|
@ -129,20 +155,13 @@ Download it from https://www.cmake.org
|
|||
if (!$?) {
|
||||
Write-Host "Command failed: " cmd /c "$PSScriptRoot\build\build_appdeps.cmd" "$Stage2Dir"
|
||||
Exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Smoke test stage2
|
||||
$env:DOTNET_HOME = "$Stage2Dir"
|
||||
& "$PSScriptRoot\test\smoke-test.ps1"
|
||||
# Run tests on stage2 dotnet tools
|
||||
& "$PSScriptRoot\test\runtests.ps1"
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: $PSScriptRoot\test\smoke-test.ps1"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# E2E Test of stage2
|
||||
& "$PSScriptRoot\test\e2e-test.ps1"
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: $PSScriptRoot\test\e2e-test.ps1"
|
||||
Write-Host "Command failed: $PSScriptRoot\test\runtests.ps1"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
|
|
|
@ -56,24 +56,27 @@ fi
|
|||
|
||||
[ -z "$CONFIGURATION" ] && export CONFIGURATION=Debug
|
||||
|
||||
# Download DNX to copy into stage2
|
||||
getDnx
|
||||
if [[ ! -z "$OFFLINE" ]]; then
|
||||
header "Skipping Stage 0, Dnx, and Packages download: Offline Build"
|
||||
else
|
||||
# Download DNX to copy into stage2
|
||||
getDnx
|
||||
|
||||
# Ensure the latest stage0 is installed
|
||||
$DIR/install.sh
|
||||
|
||||
# Ensure the latest stage0 is installed
|
||||
$DIR/install.sh
|
||||
# And put the stage0 on the PATH
|
||||
export PATH=$REPOROOT/artifacts/$RID/stage0/bin:$PATH
|
||||
|
||||
# And put the stage0 on the PATH
|
||||
export PATH=$REPOROOT/artifacts/$RID/stage0/bin:$PATH
|
||||
# Intentionally clear the DOTNET_TOOLS path, we want to use the default installed version
|
||||
unset DOTNET_TOOLS
|
||||
|
||||
# Intentionally clear the DOTNET_TOOLS path, we want to use the default installed version
|
||||
unset DOTNET_TOOLS
|
||||
DOTNET_PATH=$(which dotnet)
|
||||
PREFIX="$(cd -P "$(dirname "$DOTNET_PATH")/.." && pwd)"
|
||||
|
||||
DOTNET_PATH=$(which dotnet)
|
||||
PREFIX="$(cd -P "$(dirname "$DOTNET_PATH")/.." && pwd)"
|
||||
|
||||
header "Restoring packages"
|
||||
$DNX_ROOT/dnu restore "$REPOROOT" --quiet --runtime "$RID" --no-cache
|
||||
header "Restoring packages"
|
||||
$DNX_ROOT/dnu restore "$REPOROOT" --quiet --runtime "$RID" --no-cache
|
||||
fi
|
||||
|
||||
header "Building corehost"
|
||||
|
||||
|
@ -140,16 +143,12 @@ fi
|
|||
COMMIT_ID=$(git rev-parse HEAD)
|
||||
echo $COMMIT_ID > $STAGE2_DIR/.commit
|
||||
|
||||
# Smoke-test the output
|
||||
header "Testing stage2 ..."
|
||||
DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/smoke-test.sh
|
||||
|
||||
# Skipping E2E tests for centos
|
||||
# Skipping tests for centos
|
||||
# tracked by issue - https://github.com/dotnet/corefx/issues/5066
|
||||
if [ "$OSNAME" != "centos" ]; then
|
||||
# E2E test on the output
|
||||
header "Testing stage2 End to End ..."
|
||||
DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/e2e-test.sh
|
||||
# Run tests on the stage2 output
|
||||
header "Testing stage2..."
|
||||
DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/runtests.sh
|
||||
fi
|
||||
|
||||
# Run Validation for Project.json dependencies
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
. "$PSScriptRoot\..\_common.ps1"
|
||||
|
||||
# Restore and compile the test app
|
||||
dotnet restore "$RepoRoot\test\E2E" --runtime "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64"
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: dotnet restore"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
dotnet publish --framework dnxcore50 --runtime "$Rid" --output "$RepoRoot\artifacts\$Rid\e2etest" "$RepoRoot\test\E2E"
|
||||
if (!$?) {
|
||||
Write-Host "Command failed: dotnet publish"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
## Temporary Workaround for Native Compilation
|
||||
## Need x64 Native Tools Dev Prompt Env Vars
|
||||
## Tracked Here: https://github.com/dotnet/cli/issues/301
|
||||
pushd "$env:VS140COMNTOOLS\..\..\VC"
|
||||
cmd /c "vcvarsall.bat x64&set" |
|
||||
foreach {
|
||||
if ($_ -match "=") {
|
||||
$v = $_.split("=", 2); set-item -force -literalpath "ENV:\$($v[0])" -value "$($v[1])"
|
||||
}
|
||||
}
|
||||
popd
|
||||
|
||||
# Run the app and check the exit code
|
||||
pushd "$RepoRoot\artifacts\$Rid\e2etest"
|
||||
mv E2E.exe corehost.exe -Force
|
||||
& "corehost.exe" "xunit.console.netcore.exe" "E2E.dll" -xml ..\..\e2etest.xml
|
||||
if (!$?) {
|
||||
Write-Host "E2E Test Failure"
|
||||
popd
|
||||
Exit 1
|
||||
}
|
||||
else {
|
||||
popd
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
source "$DIR/../_common.sh"
|
||||
|
||||
rm "$REPOROOT/test/E2E/project.lock.json"
|
||||
dotnet restore --quiet "$REPOROOT/test/E2E" --runtime "$RID"
|
||||
dotnet publish --framework dnxcore50 --runtime "$RID" --output "$REPOROOT/artifacts/$RID/e2etest" "$REPOROOT/test/E2E"
|
||||
|
||||
# set -e will abort if the exit code of this is non-zero
|
||||
pushd "$REPOROOT/artifacts/$RID/e2etest"
|
||||
mv ./E2E ./corehost
|
||||
./corehost xunit.console.netcore.exe E2E.dll
|
||||
popd
|
68
scripts/test/runtests.ps1
Normal file
68
scripts/test/runtests.ps1
Normal file
|
@ -0,0 +1,68 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
. "$PSScriptRoot\..\_common.ps1"
|
||||
|
||||
$TestBinRoot = "$RepoRoot\artifacts\tests"
|
||||
|
||||
$TestProjects = @(
|
||||
"E2E",
|
||||
"Microsoft.DotNet.Tools.Publish.Tests"
|
||||
)
|
||||
|
||||
# Publish each test project
|
||||
$TestProjects | ForEach-Object {
|
||||
dotnet publish --framework "dnxcore50" --runtime "$Rid" --output "$TestBinRoot" --configuration "$Configuration" "$RepoRoot\test\$_"
|
||||
if (!$?) {
|
||||
Write-Host Command failed: dotnet publish --framework "dnxcore50" --runtime "$Rid" --output "$TestBinRoot" --configuration "$Configuration" "$RepoRoot\test\$_"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
## Temporary Workaround for Native Compilation
|
||||
## Need x64 Native Tools Dev Prompt Env Vars
|
||||
## Tracked Here: https://github.com/dotnet/cli/issues/301
|
||||
pushd "$env:VS140COMNTOOLS\..\..\VC"
|
||||
cmd /c "vcvarsall.bat x64&set" |
|
||||
foreach {
|
||||
if ($_ -match "=") {
|
||||
$v = $_.split("=", 2); set-item -force -literalpath "ENV:\$($v[0])" -value "$($v[1])"
|
||||
}
|
||||
}
|
||||
popd
|
||||
|
||||
# copy TestProjects folder which is used by the test cases
|
||||
mkdir -Force "$TestBinRoot\TestProjects"
|
||||
cp -rec -Force "$RepoRoot\test\TestProjects\*" "$TestBinRoot\TestProjects"
|
||||
|
||||
$failCount = 0
|
||||
$failingTests = @()
|
||||
|
||||
pushd "$TestBinRoot"
|
||||
|
||||
# Run each test project
|
||||
$TestProjects | ForEach-Object {
|
||||
& "corerun.exe" "xunit.console.netcore.exe" "$_.dll" -xml "$_.xml" -notrait category=failing
|
||||
$exitCode = $LastExitCode
|
||||
if ($exitCode -ne 0) {
|
||||
$failingTests += "$_"
|
||||
}
|
||||
|
||||
$failCount += $exitCode
|
||||
}
|
||||
|
||||
popd
|
||||
|
||||
if ($failCount -ne 0) {
|
||||
Write-Host -ForegroundColor Red "The following tests failed."
|
||||
$failingTests | ForEach-Object {
|
||||
Write-Host -ForegroundColor Red "$_.dll failed. Logs in '$TestBinRoot\$_.xml'"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host -ForegroundColor Green "All the tests passed!"
|
||||
}
|
||||
|
||||
Exit $failCount
|
62
scripts/test/runtests.sh
Executable file
62
scripts/test/runtests.sh
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
source "$DIR/../_common.sh"
|
||||
|
||||
|
||||
TestBinRoot="$REPOROOT/artifacts/tests"
|
||||
|
||||
TestProjects=( \
|
||||
E2E \
|
||||
Microsoft.DotNet.Tools.Publish.Tests \
|
||||
)
|
||||
|
||||
|
||||
for project in ${TestProjects[@]}
|
||||
do
|
||||
dotnet publish --framework "dnxcore50" --runtime "$RID" --output "$TestBinRoot" --configuration "$CONFIGURATION" "$REPOROOT/test/$project"
|
||||
done
|
||||
|
||||
# copy TestProjects folder which is used by the test cases
|
||||
mkdir -p "$TestBinRoot/TestProjects"
|
||||
cp -a $REPOROOT/test/TestProjects/* $TestBinRoot/TestProjects
|
||||
|
||||
|
||||
pushd "$TestBinRoot"
|
||||
set +e
|
||||
|
||||
failedTests=()
|
||||
failCount=0
|
||||
|
||||
for project in ${TestProjects[@]}
|
||||
do
|
||||
./corerun "xunit.console.netcore.exe" "$project.dll" -xml "project.xml" -notrait category=failing
|
||||
exitCode=$?
|
||||
failCount+=$exitCode
|
||||
if [ $exitCode -ne 0 ]; then
|
||||
failedTests+=($project)
|
||||
fi
|
||||
done
|
||||
|
||||
for test in ${failedTests[@]}
|
||||
do
|
||||
error "$test.dll failed. Logs in '$TestBinRoot/$test.xml'"
|
||||
done
|
||||
|
||||
popd
|
||||
set -e
|
||||
|
||||
exit $failCount
|
|
@ -1,25 +0,0 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
. "$PSScriptRoot\..\_common.ps1"
|
||||
|
||||
# Restore and compile the test app
|
||||
dotnet restore "$RepoRoot\test\TestApp" --runtime "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64"
|
||||
dotnet compile "$RepoRoot\test\TestApp" --output "$RepoRoot\artifacts\$Rid\smoketest"
|
||||
|
||||
# Run the app and check the exit code
|
||||
& "$RepoRoot\artifacts\$Rid\smoketest\TestApp.exe"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Test App failed to run"
|
||||
}
|
||||
|
||||
# Check that a compiler error is reported
|
||||
$oldErrorAction = $ErrorActionPreference
|
||||
$ErrorActionPreference="SilentlyContinue"
|
||||
dotnet compile "$RepoRoot\test\compile\failing\SimpleCompilerError" --framework "$Tfm" 2>$null >$null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
throw "Compiler error didn't cause non-zero exit code!"
|
||||
}
|
||||
$ErrorActionPreference = $oldErrorAction
|
|
@ -1,35 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
REPOROOT="$( cd -P "$DIR/../.." && pwd )"
|
||||
|
||||
source "$DIR/../_common.sh"
|
||||
|
||||
rm "$REPOROOT/test/TestApp/project.lock.json"
|
||||
dotnet restore "$REPOROOT/test/TestApp" --runtime "$RID"
|
||||
dotnet compile "$REPOROOT/test/TestApp" --output "$REPOROOT/artifacts/$RID/smoketest"
|
||||
|
||||
# set -e will abort if the exit code of this is non-zero
|
||||
$REPOROOT/artifacts/$RID/smoketest/TestApp
|
||||
|
||||
# Check that a compiler error is reported
|
||||
set +e
|
||||
dotnet compile "$REPOROOT/test/compile/failing/SimpleCompilerError" --framework "$TFM" 2>/dev/null >/dev/null
|
||||
rc=$?
|
||||
if [ $rc == 0 ]; then
|
||||
error "Compiler failure test failed! The compiler did not fail to compile!"
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
|
@ -5,7 +5,7 @@ using System.IO;
|
|||
|
||||
namespace Microsoft.DotNet.Cli.Utils
|
||||
{
|
||||
internal struct CommandResult
|
||||
public struct CommandResult
|
||||
{
|
||||
public static readonly CommandResult Empty = new CommandResult();
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
|
||||
"compilationOptions": {
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"System.Reflection": "4.0.10-rc2-23616",
|
||||
"NETStandard.Library": "1.0.0-rc2-23616",
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"compilationOptions": {
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.0.0-rc2-23616",
|
||||
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"compilationOptions": {
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.0.0-rc2-23616",
|
||||
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
||||
|
|
|
@ -29,6 +29,8 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
|
||||
public bool? EmitEntryPoint { get; set; }
|
||||
|
||||
public bool? PreserveCompilationContext { get; set; }
|
||||
|
||||
public static CommonCompilerOptions Combine(params CommonCompilerOptions[] options)
|
||||
{
|
||||
var result = new CommonCompilerOptions();
|
||||
|
@ -91,6 +93,11 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
{
|
||||
result.EmitEntryPoint = option.EmitEntryPoint;
|
||||
}
|
||||
|
||||
if (option.PreserveCompilationContext != null)
|
||||
{
|
||||
result.PreserveCompilationContext = option.PreserveCompilationContext;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.ProjectModel.Compilation;
|
||||
using Microsoft.DotNet.ProjectModel.Graph;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
public static class DependencyContextBuilder
|
||||
{
|
||||
public static DependencyContext FromLibraryExporter(LibraryExporter libraryExporter, string target, string runtime)
|
||||
{
|
||||
var dependencies = libraryExporter.GetAllExports();
|
||||
|
||||
return new DependencyContext(target, runtime,
|
||||
GetLibraries(dependencies, export => export.CompilationAssemblies),
|
||||
GetLibraries(dependencies, export => export.RuntimeAssemblies));
|
||||
}
|
||||
|
||||
private static Library[] GetLibraries(IEnumerable<LibraryExport> dependencies, Func<LibraryExport, IEnumerable<LibraryAsset>> assemblySelector)
|
||||
{
|
||||
return dependencies.Select(export => GetLibrary(export, assemblySelector(export), dependencies)).ToArray();
|
||||
}
|
||||
|
||||
private static Library GetLibrary(LibraryExport export, IEnumerable<LibraryAsset> libraryAssets, IEnumerable<LibraryExport> dependencies)
|
||||
{
|
||||
var serviceable = (export.Library as PackageDescription)?.Library.IsServiceable ?? false;
|
||||
var version = dependencies.Where(dependency => dependency.Library.Identity == export.Library.Identity);
|
||||
|
||||
var libraryDependencies = export.Library.Dependencies.Select(libraryRange => GetDependency(libraryRange, dependencies)).ToArray();
|
||||
|
||||
return new Library(
|
||||
export.Library.Identity.Type.ToString().ToLowerInvariant(),
|
||||
export.Library.Identity.Name,
|
||||
export.Library.Identity.Version.ToString(),
|
||||
export.Library.Hash,
|
||||
libraryAssets.Select(libraryAsset => libraryAsset.RelativePath).ToArray(),
|
||||
libraryDependencies,
|
||||
serviceable
|
||||
);
|
||||
}
|
||||
|
||||
private static Dependency GetDependency(LibraryRange libraryRange, IEnumerable<LibraryExport> dependencies)
|
||||
{
|
||||
var version =
|
||||
dependencies.First(d => d.Library.Identity.Name == libraryRange.Name)
|
||||
.Library.Identity.Version.ToString();
|
||||
return new Dependency(libraryRange.Name, version);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -525,7 +525,8 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
KeyFile = rawOptions.ValueAsString("keyFile"),
|
||||
DelaySign = rawOptions.ValueAsNullableBoolean("delaySign"),
|
||||
PublicSign = rawOptions.ValueAsNullableBoolean("publicSign"),
|
||||
EmitEntryPoint = rawOptions.ValueAsNullableBoolean("emitEntryPoint")
|
||||
EmitEntryPoint = rawOptions.ValueAsNullableBoolean("emitEntryPoint"),
|
||||
PreserveCompilationContext = rawOptions.ValueAsNullableBoolean("preserveCompilationContext")
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"compilationOptions": {
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"description": "Types to model a .NET Project",
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.0.0-rc2-23616",
|
||||
|
@ -19,6 +22,10 @@
|
|||
"Microsoft.Extensions.HashCodeCombiner.Sources": {
|
||||
"type": "build",
|
||||
"version": "1.0.0-*"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "build",
|
||||
"version": "1.0.0-*"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"compilationOptions": {
|
||||
"emitEntryPoint": true
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
|
|
|
@ -16,6 +16,8 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
public IEnumerable<string> LinkLibPaths { get; set; }
|
||||
public string AppDepSDKPath { get; set; }
|
||||
public string IlcPath { get; set; }
|
||||
public string IlcSdkPath { get; set; }
|
||||
public string CppCompilerFlags { get; set; }
|
||||
|
||||
public bool IsHelp { get; set; }
|
||||
public int ReturnCode { get; set; }
|
||||
|
@ -55,6 +57,12 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
if (!string.IsNullOrEmpty(IlcPath))
|
||||
{
|
||||
config.IlcPath = IlcPath;
|
||||
config.IlcSdkPath = IlcPath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(IlcSdkPath))
|
||||
{
|
||||
config.IlcSdkPath = IlcSdkPath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(LogPath))
|
||||
|
@ -67,6 +75,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
config.IlcArgs = IlcArgs;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(CppCompilerFlags))
|
||||
{
|
||||
config.CppCompilerFlags = CppCompilerFlags;
|
||||
}
|
||||
|
||||
foreach (var reference in ReferencePaths)
|
||||
{
|
||||
config.AddReference(reference);
|
||||
|
|
|
@ -17,11 +17,13 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
NativeIntermediateMode? nativeMode = null;
|
||||
string ilcArgs = null;
|
||||
string ilcPath = null;
|
||||
string ilcSdkPath = null;
|
||||
string appDepSdk = null;
|
||||
string logPath = null;
|
||||
var help = false;
|
||||
string helpText = null;
|
||||
var returnCode = 0;
|
||||
string cppCompilerFlags = null;
|
||||
|
||||
IReadOnlyList<string> references = Array.Empty<string>();
|
||||
IReadOnlyList<string> linklib = Array.Empty<string>();
|
||||
|
@ -45,7 +47,9 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
|
||||
// Custom Extensibility Points to support CoreRT workflow TODO better descriptions
|
||||
syntax.DefineOption("ilcargs", ref ilcArgs, "Use to specify custom arguments for the IL Compiler.");
|
||||
syntax.DefineOption("ilcpath", ref ilcPath, "Use to plug in a custom built ilc.exe");
|
||||
syntax.DefineOption("ilcpath", ref ilcPath, "Use to specify a custom build of IL Compiler.");
|
||||
syntax.DefineOption("ilcsdkpath", ref ilcSdkPath, "Use to specify a custom build of IL Compiler SDK");
|
||||
|
||||
syntax.DefineOptionList("linklib", ref linklib, "Use to link in additional static libs");
|
||||
|
||||
// TEMPORARY Hack until CoreRT compatible Framework Libs are available
|
||||
|
@ -54,6 +58,9 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// Optional Log Path
|
||||
syntax.DefineOption("logpath", ref logPath, "Use to dump Native Compilation Logs to a file.");
|
||||
|
||||
// Optional flags to be passed to the native compiler
|
||||
syntax.DefineOption("cppcompilerflags", ref cppCompilerFlags, "Additional flags to be passed to the native compiler.");
|
||||
|
||||
syntax.DefineOption("h|help", ref help, "Help for compile native.");
|
||||
|
||||
syntax.DefineParameter("INPUT_ASSEMBLY", ref inputAssembly,
|
||||
|
@ -125,9 +132,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
ReferencePaths = references,
|
||||
IlcArgs = ilcArgs,
|
||||
IlcPath = ilcPath,
|
||||
IlcSdkPath = ilcSdkPath,
|
||||
LinkLibPaths = linklib,
|
||||
AppDepSDKPath = appDepSdk,
|
||||
LogPath = logPath
|
||||
LogPath = logPath,
|
||||
CppCompilerFlags = cppCompilerFlags
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,11 +46,10 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
argsList.Add($"\"{inputFilePath}\"");
|
||||
|
||||
// System.Private.CoreLib Reference
|
||||
String[] coreLibs = new String[] { "System.Private.CoreLib.dll", "System.Private.Corelib.dll" };
|
||||
var coreLibPath = Path.Combine(config.IlcPath, Array.Find(coreLibs, lib => File.Exists(Path.Combine(config.IlcPath, lib))));
|
||||
var coreLibPath = Path.Combine(config.IlcSdkPath, "System.Private.CoreLib.dll");
|
||||
argsList.Add($"-r \"{coreLibPath}\"");
|
||||
|
||||
// Dependency References
|
||||
// AppDep References
|
||||
foreach (var reference in config.ReferencePaths)
|
||||
{
|
||||
argsList.Add($"-r \"{reference}\"");
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
private readonly string cLibsFlags = "-lm -ldl";
|
||||
private readonly string cflags = "-g -lstdc++ -lrt -Wno-invalid-offsetof -pthread";
|
||||
|
||||
private readonly string[] libs = new string[]
|
||||
private readonly string[] IlcSdkLibs = new string[]
|
||||
{
|
||||
"libbootstrappercpp.a",
|
||||
"libPortableRuntime.a",
|
||||
|
@ -65,24 +65,29 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// Flags
|
||||
argsList.Add(cflags);
|
||||
|
||||
// Add Includes
|
||||
// TODO: Enable this when https://github.com/dotnet/cli/pull/469 goes through.
|
||||
// var ilcSdkIncPath = Path.Combine(config.IlcSdkPath, "inc");
|
||||
//
|
||||
// Get the directory name to ensure there are no trailing slashes as they may conflict
|
||||
// with the terminating " we suffix to account for paths with spaces in them.
|
||||
var ilcSdkIncPath = Path.GetDirectoryName(config.IlcSdkPath);
|
||||
argsList.Add("-I");
|
||||
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/ubuntu.14.04"));
|
||||
|
||||
argsList.Add("-I");
|
||||
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk"));
|
||||
argsList.Add($"\"{ilcSdkIncPath}\"");
|
||||
|
||||
// Input File
|
||||
var inCppFile = DetermineInFile(config);
|
||||
argsList.Add(inCppFile);
|
||||
|
||||
// Add Stubs
|
||||
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/ubuntu.14.04/lxstubs.cpp"));
|
||||
|
||||
// Libs
|
||||
foreach (var lib in libs)
|
||||
// Pass the optional native compiler flags if specified
|
||||
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
|
||||
{
|
||||
var libPath = Path.Combine(config.IlcPath, lib);
|
||||
argsList.Add(config.CppCompilerFlags);
|
||||
}
|
||||
|
||||
// ILC SDK Libs
|
||||
foreach (var lib in IlcSdkLibs)
|
||||
{
|
||||
var libPath = Path.Combine(config.IlcSdkPath, lib);
|
||||
argsList.Add(libPath);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// TODO: debug/release support
|
||||
private readonly string cflags = "-lstdc++ -lpthread -ldl -lm -lrt";
|
||||
|
||||
private readonly string[] libs = new string[]
|
||||
private readonly string[] IlcSdkLibs = new string[]
|
||||
{
|
||||
"libbootstrapper.a",
|
||||
"libRuntime.a",
|
||||
|
@ -70,10 +70,16 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
var inLibFile = DetermineInFile(config);
|
||||
argsList.Add(inLibFile);
|
||||
|
||||
// Libs
|
||||
foreach (var lib in libs)
|
||||
// Pass the optional native compiler flags if specified
|
||||
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
|
||||
{
|
||||
var libPath = Path.Combine(config.IlcPath, lib);
|
||||
argsList.Add(config.CppCompilerFlags);
|
||||
}
|
||||
|
||||
// ILC SDK Libs
|
||||
foreach (var lib in IlcSdkLibs)
|
||||
{
|
||||
var libPath = Path.Combine(config.IlcSdkPath, lib);
|
||||
argsList.Add(libPath);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// Link to iconv APIs
|
||||
private readonly string libFlags = "-liconv";
|
||||
|
||||
private readonly string[] libs = new string[]
|
||||
private readonly string[] IlcSdkLibs = new string[]
|
||||
{
|
||||
"libbootstrappercpp.a",
|
||||
"libPortableRuntime.a",
|
||||
|
@ -67,27 +67,32 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// Flags
|
||||
argsList.Add(cflags);
|
||||
|
||||
// Add Includes
|
||||
// TODO: Enable this when https://github.com/dotnet/cli/pull/469 goes through.
|
||||
// var ilcSdkIncPath = Path.Combine(config.IlcSdkPath, "inc");
|
||||
//
|
||||
// Get the directory name to ensure there are no trailing slashes as they may conflict
|
||||
// with the terminating " we suffix to account for paths with spaces in them.
|
||||
var ilcSdkIncPath = Path.GetDirectoryName(config.IlcSdkPath);
|
||||
argsList.Add("-I");
|
||||
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10"));
|
||||
|
||||
argsList.Add("-I");
|
||||
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk"));
|
||||
argsList.Add($"\"{ilcSdkIncPath}\"");
|
||||
|
||||
// Input File
|
||||
var inCppFile = DetermineInFile(config);
|
||||
argsList.Add(inCppFile);
|
||||
|
||||
// Add Stubs
|
||||
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10/osxstubs.cpp"));
|
||||
|
||||
// Lib flags
|
||||
argsList.Add(libFlags);
|
||||
|
||||
// Libs
|
||||
foreach (var lib in libs)
|
||||
// Pass the optional native compiler flags if specified
|
||||
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
|
||||
{
|
||||
var libPath = Path.Combine(config.IlcPath, lib);
|
||||
argsList.Add(config.CppCompilerFlags);
|
||||
}
|
||||
|
||||
// ILC SDK Libs
|
||||
foreach (var lib in IlcSdkLibs)
|
||||
{
|
||||
var libPath = Path.Combine(config.IlcSdkPath, lib);
|
||||
|
||||
// Forward the library to linked to the linker
|
||||
argsList.Add("-Xlinker");
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// TODO: debug/release support
|
||||
private readonly string cflags = "-g -lstdc++ -Wno-invalid-offsetof -pthread -ldl -lm -liconv";
|
||||
|
||||
private readonly string[] libs = new string[]
|
||||
private readonly string[] IlcSdkLibs = new string[]
|
||||
{
|
||||
"libbootstrapper.a",
|
||||
"libRuntime.a",
|
||||
|
@ -66,19 +66,20 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// Flags
|
||||
argsList.Add(cflags);
|
||||
|
||||
// Add Stubs
|
||||
argsList.Add("-I "+Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10"));
|
||||
argsList.Add("-I "+Path.Combine(config.AppDepSDKPath, "CPPSdk"));
|
||||
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10/osxstubs.cpp"));
|
||||
|
||||
// Pass the optional native compiler flags if specified
|
||||
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
|
||||
{
|
||||
argsList.Add(config.CppCompilerFlags);
|
||||
}
|
||||
|
||||
// Input File
|
||||
var inLibFile = DetermineInFile(config);
|
||||
argsList.Add("-Xlinker "+inLibFile);
|
||||
|
||||
// Libs
|
||||
foreach (var lib in libs)
|
||||
// ILC SDK Libs
|
||||
foreach (var lib in IlcSdkLibs)
|
||||
{
|
||||
var libPath = Path.Combine(config.IlcPath, lib);
|
||||
var libPath = Path.Combine(config.IlcSdkPath, lib);
|
||||
argsList.Add("-Xlinker "+libPath);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
|
||||
private static readonly Dictionary<BuildConfiguration, string> ConfigurationCompilerOptionsMap = new Dictionary<BuildConfiguration, string>
|
||||
{
|
||||
{ BuildConfiguration.debug, "/ZI /nologo /W3 /WX- /sdl /Od /D CPPCODEGEN /D WIN32 /D _DEBUG /D _CONSOLE /D _LIB /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Gd /TP /wd4477 /errorReport:prompt" },
|
||||
{ BuildConfiguration.debug, "/ZI /nologo /W3 /WX- /sdl /Od /D CPPCODEGEN /D WIN32 /D _CONSOLE /D _LIB /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MD /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Gd /TP /wd4477 /errorReport:prompt" },
|
||||
{ BuildConfiguration.release, "/Zi /nologo /W3 /WX- /sdl /O2 /Oi /GL /D CPPCODEGEN /D WIN32 /D NDEBUG /D _CONSOLE /D _LIB /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Gd /TP /wd4477 /errorReport:prompt" }
|
||||
};
|
||||
|
||||
|
@ -68,17 +68,26 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
argsList.Add("/c");
|
||||
|
||||
// Add Includes
|
||||
var win7CppSdkPath = Path.Combine(config.AppDepSDKPath, "CPPSdk\\win7");
|
||||
//
|
||||
// TODO: Enable this when https://github.com/dotnet/cli/pull/469 goes through.
|
||||
// var ilcSdkIncPath = Path.Combine(config.IlcSdkPath, "inc");
|
||||
//
|
||||
// Get the directory name to ensure there are no trailing slashes as they may conflict
|
||||
// with the terminating " we suffix to account for paths with spaces in them.
|
||||
var ilcSdkIncPath = config.IlcSdkPath;
|
||||
ilcSdkIncPath = ilcSdkIncPath.TrimEnd(new char[] {'\\'});
|
||||
argsList.Add("/I");
|
||||
argsList.Add($"\"{win7CppSdkPath}\"");
|
||||
|
||||
var cppSdkPath = Path.Combine(config.AppDepSDKPath, "CPPSdk");
|
||||
argsList.Add("/I");
|
||||
argsList.Add($"\"{cppSdkPath}\"");
|
||||
argsList.Add($"\"{ilcSdkIncPath}\"");
|
||||
|
||||
// Configuration Based Compiler Options
|
||||
argsList.Add(ConfigurationCompilerOptionsMap[config.BuildType]);
|
||||
|
||||
// Pass the optional native compiler flags if specified
|
||||
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
|
||||
{
|
||||
argsList.Add(config.CppCompilerFlags);
|
||||
}
|
||||
|
||||
// Output
|
||||
var objOut = DetermineOutputFile(config);
|
||||
argsList.Add($"/Fo\"{objOut}\"");
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
{ BuildConfiguration.release, "/NOLOGO /ERRORREPORT:PROMPT /INCREMENTAL:NO /OPT:REF /OPT:ICF /LTCG:incremental /MANIFEST /MANIFESTUAC:\"level='asInvoker' uiAccess='false'\" /manifest:embed /Debug /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT" }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<NativeIntermediateMode, string[]> ModeLibMap = new Dictionary<NativeIntermediateMode, string[]>
|
||||
private static readonly Dictionary<NativeIntermediateMode, string[]> IlcSdkLibMap = new Dictionary<NativeIntermediateMode, string[]>
|
||||
{
|
||||
{ NativeIntermediateMode.cpp, new string[] { "PortableRuntime.lib", "bootstrappercpp.lib" } },
|
||||
{ NativeIntermediateMode.ryujit, new string[] { "Runtime.lib", "bootstrapper.lib" } }
|
||||
|
@ -46,9 +46,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
"odbccp32.lib"
|
||||
};
|
||||
|
||||
// We will always link against msvcrt.lib since the runtime libraries are also built against msvcrt.lib as we are not interested in assertions
|
||||
// from CRT code.
|
||||
private static readonly Dictionary<BuildConfiguration, string[]> ConfigurationLinkLibMap = new Dictionary<BuildConfiguration, string[]>()
|
||||
{
|
||||
{ BuildConfiguration.debug , new string[] { "msvcrtd.lib" } },
|
||||
{ BuildConfiguration.debug , new string[] { "msvcrt.lib" } },
|
||||
{ BuildConfiguration.release , new string[] { "msvcrt.lib" } }
|
||||
};
|
||||
|
||||
|
@ -98,11 +100,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
// Constant Libs
|
||||
argsList.Add(string.Join(" ", ConstantLinkLibs));
|
||||
|
||||
// SDK Libs
|
||||
var SDKLibs = ModeLibMap[config.NativeMode];
|
||||
// ILC SDK Libs
|
||||
var SDKLibs = IlcSdkLibMap[config.NativeMode];
|
||||
foreach (var lib in SDKLibs)
|
||||
{
|
||||
var sdkLibPath = Path.Combine(config.IlcPath, lib);
|
||||
var sdkLibPath = Path.Combine(config.IlcSdkPath, lib);
|
||||
argsList.Add($"\"{sdkLibPath}\"");
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,14 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
private string _inputManagedAssemblyPath;
|
||||
private string _appDepSdkPath;
|
||||
private string _ilcPath;
|
||||
private string _ilcSdkPath;
|
||||
private string _outputDirectory;
|
||||
private string _intermediateDirectory;
|
||||
private string _logPath;
|
||||
private string _ilcArgs;
|
||||
private readonly List<string> _referencePaths;
|
||||
private readonly List<string> _linkLibPaths;
|
||||
private string _cppCompilerFlags;
|
||||
|
||||
public string LogPath
|
||||
{
|
||||
|
@ -129,12 +131,44 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
|
|||
}
|
||||
}
|
||||
|
||||
public string IlcSdkPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ilcSdkPath;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!Directory.Exists(value))
|
||||
{
|
||||
throw new Exception($"ILC SDK Directory does not exist: {value}.");
|
||||
}
|
||||
|
||||
_ilcSdkPath = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string CppCompilerFlags
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cppCompilerFlags;
|
||||
}
|
||||
set
|
||||
{
|
||||
_cppCompilerFlags = value;
|
||||
}
|
||||
}
|
||||
|
||||
private NativeCompileSettings()
|
||||
{
|
||||
_linkLibPaths = new List<string>();
|
||||
_referencePaths = new List<string>();
|
||||
|
||||
IlcPath = AppContext.BaseDirectory;
|
||||
|
||||
// By default, ILC SDK Path is assumed to be the same folder as ILC path
|
||||
IlcSdkPath = IlcPath;
|
||||
Architecture = DefaultArchitectureMode;
|
||||
BuildType = DefaultBuiltType;
|
||||
NativeMode = DefaultNativeModel;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"emitEntryPoint": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.AppDep":"1.0.2-*"
|
||||
"Microsoft.DotNet.AppDep":"1.0.3-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnxcore50": { }
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
"type": "build",
|
||||
"version": "1.0.0-*"
|
||||
},
|
||||
"Microsoft.DotNet.ILCompiler": "1.0.3-*",
|
||||
"Microsoft.DotNet.ILCompiler.SDK": "1.0.3-*",
|
||||
"Microsoft.DotNet.ILCompiler": "1.0.4-*",
|
||||
"Microsoft.DotNet.ILCompiler.SDK": "1.0.4-*",
|
||||
"Microsoft.DotNet.Compiler.Common": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
@ -16,6 +16,7 @@ using Microsoft.DotNet.ProjectModel;
|
|||
using Microsoft.DotNet.ProjectModel.Compilation;
|
||||
using Microsoft.DotNet.ProjectModel.Utilities;
|
||||
using NuGet.Frameworks;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Compiler
|
||||
{
|
||||
|
@ -44,8 +45,10 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
var arch = app.Option("-a|--arch <ARCH>", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue);
|
||||
var ilcArgs = app.Option("--ilcargs <ARGS>", "Command line arguments to be passed directly to ILCompiler.", CommandOptionType.SingleValue);
|
||||
var ilcPath = app.Option("--ilcpath <PATH>", "Path to the folder containing custom built ILCompiler.", CommandOptionType.SingleValue);
|
||||
var ilcSdkPath = app.Option("--ilcsdkpath <PATH>", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue);
|
||||
var ilcSdkPath = app.Option("--ilcsdkpath <PATH>", "Path to the folder containing custom built ILCompiler SDK.", CommandOptionType.SingleValue);
|
||||
var appDepSdkPath = app.Option("--appdepsdkpath <PATH>", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue);
|
||||
var cppMode = app.Option("--cpp", "Flag to do native compilation with C++ code generator.", CommandOptionType.NoValue);
|
||||
var cppCompilerFlags = app.Option("--cppcompilerflags <flags>", "Additional flags to be passed to the native compiler.", CommandOptionType.SingleValue);
|
||||
|
||||
app.OnExecute(() =>
|
||||
{
|
||||
|
@ -63,9 +66,11 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
var ilcArgsValue = ilcArgs.Value();
|
||||
var ilcPathValue = ilcPath.Value();
|
||||
var ilcSdkPathValue = ilcSdkPath.Value();
|
||||
var appDepSdkPathValue = appDepSdkPath.Value();
|
||||
var configValue = configuration.Value() ?? Constants.DefaultConfiguration;
|
||||
var outputValue = output.Value();
|
||||
var intermediateValue = intermediateOutput.Value();
|
||||
var cppCompilerFlagsValue = cppCompilerFlags.Value();
|
||||
|
||||
// Load project contexts for each framework and compile them
|
||||
bool success = true;
|
||||
|
@ -77,7 +82,7 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
success &= Compile(context, configValue, outputValue, intermediateValue, buildProjectReferences, noHost.HasValue());
|
||||
if (isNative && success)
|
||||
{
|
||||
success &= CompileNative(context, configValue, outputValue, buildProjectReferences, intermediateValue, archValue, ilcArgsValue, ilcPathValue, ilcSdkPathValue, isCppMode);
|
||||
success &= CompileNative(context, configValue, outputValue, buildProjectReferences, intermediateValue, archValue, ilcArgsValue, ilcPathValue, ilcSdkPathValue, appDepSdkPathValue, isCppMode, cppCompilerFlagsValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +114,9 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
string ilcArgsValue,
|
||||
string ilcPathValue,
|
||||
string ilcSdkPathValue,
|
||||
bool isCppMode)
|
||||
string appDepSdkPathValue,
|
||||
bool isCppMode,
|
||||
string cppCompilerFlagsValue)
|
||||
{
|
||||
var outputPath = GetOutputPath(context, configuration, outputOptionValue);
|
||||
var nativeOutputPath = Path.Combine(GetOutputPath(context, configuration, outputOptionValue), "native");
|
||||
|
@ -145,10 +152,17 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
// ILC SDK Path
|
||||
if (!string.IsNullOrWhiteSpace(ilcSdkPathValue))
|
||||
{
|
||||
nativeArgs.Add("--appdepsdk");
|
||||
nativeArgs.Add("--ilcsdkpath");
|
||||
nativeArgs.Add(ilcSdkPathValue);
|
||||
}
|
||||
|
||||
// AppDep SDK Path
|
||||
if (!string.IsNullOrWhiteSpace(appDepSdkPathValue))
|
||||
{
|
||||
nativeArgs.Add("--appdepsdk");
|
||||
nativeArgs.Add(appDepSdkPathValue);
|
||||
}
|
||||
|
||||
// CodeGen Mode
|
||||
if(isCppMode)
|
||||
{
|
||||
|
@ -156,6 +170,12 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
nativeArgs.Add("cpp");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cppCompilerFlagsValue))
|
||||
{
|
||||
nativeArgs.Add("--cppcompilerflags");
|
||||
nativeArgs.Add(cppCompilerFlagsValue);
|
||||
}
|
||||
|
||||
// Configuration
|
||||
if (configuration != null)
|
||||
{
|
||||
|
@ -304,6 +324,8 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
compilationOptions.KeyFile = Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile));
|
||||
}
|
||||
|
||||
var references = new List<string>();
|
||||
|
||||
// Add compilation options to the args
|
||||
compilerArgs.AddRange(compilationOptions.SerializeToArgs());
|
||||
|
||||
|
@ -319,16 +341,49 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
if (projectDependency.Project.Files.SourceFiles.Any())
|
||||
{
|
||||
var projectOutputPath = GetProjectOutput(projectDependency.Project, projectDependency.Framework, configuration, outputPath);
|
||||
compilerArgs.Add($"--reference:{projectOutputPath}");
|
||||
references.Add(projectOutputPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
compilerArgs.AddRange(dependency.CompilationAssemblies.Select(r => $"--reference:{r.ResolvedPath}"));
|
||||
references.AddRange(dependency.CompilationAssemblies.Select(r => r.ResolvedPath));
|
||||
}
|
||||
|
||||
compilerArgs.AddRange(dependency.SourceReferences);
|
||||
}
|
||||
|
||||
compilerArgs.AddRange(references.Select(r => $"--reference:{r}"));
|
||||
|
||||
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
|
||||
var libraryExporter = runtimeContext.CreateExporter(configuration);
|
||||
|
||||
if (compilationOptions.PreserveCompilationContext == true)
|
||||
{
|
||||
var dependencyContext = DependencyContextBuilder.FromLibraryExporter(
|
||||
libraryExporter, context.TargetFramework.DotNetFrameworkName, context.RuntimeIdentifier);
|
||||
|
||||
var writer = new DependencyContextWriter();
|
||||
var depsJsonFile = Path.Combine(intermediateOutputPath, context.ProjectFile.Name + "dotnet-compile.deps.json");
|
||||
using (var fileStream = File.Create(depsJsonFile))
|
||||
{
|
||||
writer.Write(dependencyContext, fileStream);
|
||||
}
|
||||
|
||||
compilerArgs.Add($"--resource:\"{depsJsonFile}\",{context.ProjectFile.Name}.deps.json");
|
||||
|
||||
var refsFolder = Path.Combine(outputPath, "refs");
|
||||
if (Directory.Exists(refsFolder))
|
||||
{
|
||||
Directory.Delete(refsFolder, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(refsFolder);
|
||||
foreach (var reference in references)
|
||||
{
|
||||
File.Copy(reference, Path.Combine(refsFolder, Path.GetFileName(reference)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!AddResources(context.ProjectFile, compilerArgs, intermediateOutputPath))
|
||||
{
|
||||
return false;
|
||||
|
@ -394,10 +449,9 @@ namespace Microsoft.DotNet.Tools.Compiler
|
|||
|
||||
if (success && !noHost && compilationOptions.EmitEntryPoint.GetValueOrDefault())
|
||||
{
|
||||
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
|
||||
MakeRunnable(runtimeContext,
|
||||
outputPath,
|
||||
runtimeContext.CreateExporter(configuration));
|
||||
libraryExporter);
|
||||
}
|
||||
|
||||
return PrintSummary(diagnostics, sw, success);
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
"Microsoft.Extensions.CommandLineUtils.Sources": {
|
||||
"type": "build",
|
||||
"version": "1.0.0-*"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"type": "build",
|
||||
"version": "1.0.0-*"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
@ -28,7 +28,9 @@ namespace Microsoft.DotNet.Tools.New
|
|||
{
|
||||
var thisAssembly = typeof(Program).GetTypeInfo().Assembly;
|
||||
var resources = from resourceName in thisAssembly.GetManifestResourceNames()
|
||||
where resourceName.ToLowerInvariant().EndsWith(".cs") || resourceName.ToLowerInvariant().EndsWith(".json")
|
||||
where resourceName.ToLowerInvariant().EndsWith(".cs")
|
||||
|| resourceName.ToLowerInvariant().EndsWith(".json")
|
||||
|| resourceName.ToLowerInvariant().EndsWith(".config")
|
||||
select resourceName;
|
||||
|
||||
var resourceNameToFileName = new Dictionary<string, string>();
|
||||
|
|
9
src/Microsoft.DotNet.Tools.New/Template/NuGet.Config
Normal file
9
src/Microsoft.DotNet.Tools.New/Template/NuGet.Config
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
|
||||
<clear />
|
||||
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
|
||||
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
|
@ -13,20 +13,46 @@ namespace Microsoft.DotNet.Tools.Run
|
|||
{
|
||||
DebugHelper.HandleDebugSwitch(ref args);
|
||||
|
||||
var help = false;
|
||||
string helpText = null;
|
||||
var returnCode = 0;
|
||||
|
||||
RunCommand runCmd = new RunCommand();
|
||||
|
||||
ArgumentSyntax.Parse(args, syntax =>
|
||||
try
|
||||
{
|
||||
syntax.HandleErrors = false;
|
||||
syntax.DefineOption("f|framework", ref runCmd.Framework, "Compile a specific framework");
|
||||
syntax.DefineOption("c|configuration", ref runCmd.Configuration, "Configuration under which to build");
|
||||
syntax.DefineOption("t|preserve-temporary", ref runCmd.PreserveTemporary, "Keep the output's temporary directory around");
|
||||
syntax.DefineOption("p|project", ref runCmd.Project, "The path to the project to run (defaults to the current directory). Can be a path to a project.json or a project directory");
|
||||
ArgumentSyntax.Parse(args, syntax =>
|
||||
{
|
||||
syntax.HandleHelp = false;
|
||||
syntax.HandleErrors = false;
|
||||
|
||||
// TODO: this is not supporting args which can be switches (i.e. --test)
|
||||
// TODO: we need to make a change in System.CommandLine or parse args ourselves.
|
||||
syntax.DefineParameterList("args", ref runCmd.Args, "Arguments to pass to the executable or script");
|
||||
});
|
||||
syntax.DefineOption("f|framework", ref runCmd.Framework, "Compile a specific framework");
|
||||
syntax.DefineOption("c|configuration", ref runCmd.Configuration, "Configuration under which to build");
|
||||
syntax.DefineOption("t|preserve-temporary", ref runCmd.PreserveTemporary, "Keep the output's temporary directory around");
|
||||
syntax.DefineOption("p|project", ref runCmd.Project, "The path to the project to run (defaults to the current directory). Can be a path to a project.json or a project directory");
|
||||
|
||||
syntax.DefineOption("h|help", ref help, "Help for compile native.");
|
||||
|
||||
// TODO: this is not supporting args which can be switches (i.e. --test)
|
||||
// TODO: we need to make a change in System.CommandLine or parse args ourselves.
|
||||
syntax.DefineParameterList("args", ref runCmd.Args, "Arguments to pass to the executable or script");
|
||||
|
||||
helpText = syntax.GetHelpText();
|
||||
});
|
||||
}
|
||||
catch (ArgumentSyntaxException exception)
|
||||
{
|
||||
Console.Error.WriteLine(exception.Message);
|
||||
help = true;
|
||||
returnCode = 1;
|
||||
}
|
||||
|
||||
if (help)
|
||||
{
|
||||
Console.WriteLine(helpText);
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
17
src/Microsoft.Extensions.DependencyModel/Dependency.cs
Normal file
17
src/Microsoft.Extensions.DependencyModel/Dependency.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
public struct Dependency
|
||||
{
|
||||
public Dependency(string name, string version)
|
||||
{
|
||||
Name = name;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string Version { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// 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.Reflection;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
public class DependencyContext
|
||||
{
|
||||
private const string DepsResourceSufix = ".deps.json";
|
||||
|
||||
public DependencyContext(string target, string runtime, Library[] compileLibraries, Library[] runtimeLibraries)
|
||||
{
|
||||
Target = target;
|
||||
Runtime = runtime;
|
||||
CompileLibraries = compileLibraries;
|
||||
RuntimeLibraries = runtimeLibraries;
|
||||
}
|
||||
|
||||
public string Target { get; set; }
|
||||
|
||||
public string Runtime { get; set; }
|
||||
|
||||
public IReadOnlyList<Library> CompileLibraries { get; }
|
||||
|
||||
public IReadOnlyList<Library> RuntimeLibraries { get; }
|
||||
|
||||
public static DependencyContext Load()
|
||||
{
|
||||
var entryAssembly = (Assembly)typeof(Assembly).GetTypeInfo().GetDeclaredMethod("GetEntryAssembly").Invoke(null, null);
|
||||
var stream = entryAssembly.GetManifestResourceStream(entryAssembly.GetName().Name + DepsResourceSufix);
|
||||
|
||||
if (stream == null)
|
||||
{
|
||||
throw new InvalidOperationException("Entry assembly was compiled without `preserveCompilationContext` enabled");
|
||||
}
|
||||
|
||||
using (stream)
|
||||
{
|
||||
return Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static DependencyContext Load(Stream stream)
|
||||
{
|
||||
return new DependencyContextReader().Read(stream);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
// 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 Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
public class DependencyContextReader
|
||||
{
|
||||
public DependencyContext Read(Stream stream)
|
||||
{
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
using (var reader = new JsonTextReader(streamReader))
|
||||
{
|
||||
var root = JObject.Load(reader);
|
||||
return Read(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsRuntimeTarget(string name) => name.Contains(DependencyContextStrings.VersionSeperator);
|
||||
|
||||
private DependencyContext Read(JObject root)
|
||||
{
|
||||
var libraryStubs = ReadLibraryStubs((JObject) root[DependencyContextStrings.LibrariesPropertyName]);
|
||||
var targetsObject = (IEnumerable<KeyValuePair<string, JToken>>) root[DependencyContextStrings.TargetsPropertyName];
|
||||
|
||||
var runtimeTargetProperty = targetsObject.First(target => IsRuntimeTarget(target.Key));
|
||||
var compileTargetProperty = targetsObject.First(target => !IsRuntimeTarget(target.Key));
|
||||
|
||||
return new DependencyContext(
|
||||
compileTargetProperty.Key,
|
||||
runtimeTargetProperty.Key.Substring(compileTargetProperty.Key.Length + 1),
|
||||
ReadLibraries((JObject)runtimeTargetProperty.Value, true, libraryStubs),
|
||||
ReadLibraries((JObject)compileTargetProperty.Value, false, libraryStubs)
|
||||
);
|
||||
}
|
||||
|
||||
private Library[] ReadLibraries(JObject librariesObject, bool runtime, Dictionary<string, DependencyContextReader.LibraryStub> libraryStubs)
|
||||
{
|
||||
return librariesObject.Properties().Select(property => ReadLibrary(property, runtime, libraryStubs)).ToArray();
|
||||
}
|
||||
|
||||
private Library ReadLibrary(JProperty property, bool runtime, Dictionary<string, DependencyContextReader.LibraryStub> libraryStubs)
|
||||
{
|
||||
var nameWithVersion = property.Name;
|
||||
DependencyContextReader.LibraryStub stub;
|
||||
|
||||
if (!libraryStubs.TryGetValue(nameWithVersion, out stub))
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot find library information for {nameWithVersion}");
|
||||
}
|
||||
|
||||
var seperatorPosition = nameWithVersion.IndexOf(DependencyContextStrings.VersionSeperator);
|
||||
|
||||
var name = nameWithVersion.Substring(0, seperatorPosition);
|
||||
var version = nameWithVersion.Substring(seperatorPosition + 1);
|
||||
|
||||
var libraryObject = (JObject) property.Value;
|
||||
|
||||
var dependencies = ReadDependencies(libraryObject);
|
||||
var assemblies = ReadAssemblies(libraryObject, runtime);
|
||||
|
||||
return new Library(stub.Type, name, version, stub.Hash, assemblies, dependencies, stub.Serviceable);
|
||||
}
|
||||
|
||||
private static string[] ReadAssemblies(JObject libraryObject, bool runtime)
|
||||
{
|
||||
var assembliesObject = (JObject) libraryObject[runtime ? DependencyContextStrings.RunTimeAssembliesKey : DependencyContextStrings.CompileTimeAssembliesKey];
|
||||
|
||||
if (assembliesObject == null)
|
||||
{
|
||||
return new string[] {};
|
||||
}
|
||||
|
||||
return assembliesObject.Properties().Select(property => property.Name).ToArray();
|
||||
}
|
||||
|
||||
private static Dependency[] ReadDependencies(JObject libraryObject)
|
||||
{
|
||||
var dependenciesObject = ((JObject) libraryObject[DependencyContextStrings.DependenciesPropertyName]);
|
||||
|
||||
if (dependenciesObject == null)
|
||||
{
|
||||
return new Dependency[]{ };
|
||||
}
|
||||
|
||||
return dependenciesObject.Properties()
|
||||
.Select(property => new Dependency(property.Name, (string) property.Value)).ToArray();
|
||||
}
|
||||
|
||||
private Dictionary<string, LibraryStub> ReadLibraryStubs(JObject librariesObject)
|
||||
{
|
||||
var libraries = new Dictionary<string, LibraryStub>();
|
||||
foreach (var libraryProperty in librariesObject)
|
||||
{
|
||||
var value = (JObject) libraryProperty.Value;
|
||||
var stub = new LibraryStub
|
||||
{
|
||||
Name = libraryProperty.Key,
|
||||
Hash = value[DependencyContextStrings.Sha512PropertyName]?.Value<string>(),
|
||||
Type = value[DependencyContextStrings.TypePropertyName].Value<string>(),
|
||||
Serviceable = value[DependencyContextStrings.ServiceablePropertyName]?.Value<bool>() == true
|
||||
};
|
||||
libraries.Add(stub.Name, stub);
|
||||
}
|
||||
return libraries;
|
||||
}
|
||||
|
||||
private struct LibraryStub
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public string Hash;
|
||||
|
||||
public string Type;
|
||||
|
||||
public bool Serviceable;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal class DependencyContextStrings
|
||||
{
|
||||
internal const char VersionSeperator = '/';
|
||||
|
||||
internal const string CompileTimeAssembliesKey = "compile";
|
||||
|
||||
internal const string RunTimeAssembliesKey = "runtime";
|
||||
|
||||
internal const string LibrariesPropertyName = "libraries";
|
||||
|
||||
internal const string TargetsPropertyName = "targets";
|
||||
|
||||
internal const string DependenciesPropertyName = "dependencies";
|
||||
|
||||
internal const string Sha512PropertyName = "sha512";
|
||||
|
||||
internal const string TypePropertyName = "type";
|
||||
|
||||
internal const string ServiceablePropertyName = "serviceable";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
public class DependencyContextWriter
|
||||
{
|
||||
public void Write(DependencyContext context, Stream stream)
|
||||
{
|
||||
using (var writer = new StreamWriter(stream))
|
||||
{
|
||||
using (var jsonWriter = new JsonTextWriter(writer))
|
||||
{
|
||||
Write(context).WriteTo(jsonWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JObject Write(DependencyContext context)
|
||||
{
|
||||
return new JObject(
|
||||
new JProperty(DependencyContextStrings.TargetsPropertyName, WriteTargets(context)),
|
||||
new JProperty(DependencyContextStrings.LibrariesPropertyName, WriteLibraries(context))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private JObject WriteTargets(DependencyContext context)
|
||||
{
|
||||
return new JObject(
|
||||
new JProperty(context.Target, WriteTarget(context.CompileLibraries, false)),
|
||||
new JProperty(context.Target + DependencyContextStrings.VersionSeperator + context.Runtime,
|
||||
WriteTarget(context.RuntimeLibraries, true))
|
||||
);
|
||||
}
|
||||
|
||||
private JObject WriteTarget(IReadOnlyList<Library> libraries, bool runtime)
|
||||
{
|
||||
return new JObject(
|
||||
libraries.Select(library =>
|
||||
new JProperty(library.PackageName + DependencyContextStrings.VersionSeperator + library.Version, WriteTargetLibrary(library, runtime))));
|
||||
}
|
||||
|
||||
private JObject WriteTargetLibrary(Library library, bool runtime)
|
||||
{
|
||||
return new JObject(
|
||||
new JProperty(DependencyContextStrings.DependenciesPropertyName, WriteDependencies(library.Dependencies)),
|
||||
new JProperty(runtime ? DependencyContextStrings.RunTimeAssembliesKey : DependencyContextStrings.CompileTimeAssembliesKey,
|
||||
WriteAssemblies(library.Assemblies))
|
||||
);
|
||||
}
|
||||
|
||||
private JObject WriteAssemblies(IReadOnlyList<string> assemblies)
|
||||
{
|
||||
return new JObject(assemblies.Select(assembly => new JProperty(assembly, new JObject())));
|
||||
}
|
||||
|
||||
private JObject WriteDependencies(IReadOnlyList<Dependency> dependencies)
|
||||
{
|
||||
return new JObject(
|
||||
dependencies.Select(dependency => new JProperty(dependency.Name, dependency.Version))
|
||||
);
|
||||
}
|
||||
|
||||
private JObject WriteLibraries(DependencyContext context)
|
||||
{
|
||||
var allLibraries =
|
||||
context.RuntimeLibraries.Concat(context.CompileLibraries)
|
||||
.GroupBy(library => library.PackageName + DependencyContextStrings.VersionSeperator + library.Version);
|
||||
|
||||
return new JObject(allLibraries.Select(libraries=> new JProperty(libraries.Key, WriteLibrary(libraries.First()))));
|
||||
}
|
||||
|
||||
private JObject WriteLibrary(Library library)
|
||||
{
|
||||
return new JObject(
|
||||
new JProperty(DependencyContextStrings.TypePropertyName, library.LibraryType),
|
||||
new JProperty(DependencyContextStrings.ServiceablePropertyName, library.Serviceable),
|
||||
new JProperty(DependencyContextStrings.Sha512PropertyName, library.Hash)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
36
src/Microsoft.Extensions.DependencyModel/Library.cs
Normal file
36
src/Microsoft.Extensions.DependencyModel/Library.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
public struct Library
|
||||
{
|
||||
public Library(string libraryType, string packageName, string version, string hash, string[] assemblies, Dependency[] dependencies, bool serviceable)
|
||||
{
|
||||
LibraryType = libraryType;
|
||||
PackageName = packageName;
|
||||
Version = version;
|
||||
Hash = hash;
|
||||
Assemblies = assemblies;
|
||||
Dependencies = dependencies;
|
||||
Serviceable = serviceable;
|
||||
}
|
||||
|
||||
public string LibraryType { get; }
|
||||
|
||||
public string PackageName { get; }
|
||||
|
||||
public string Version { get; }
|
||||
|
||||
public string Hash { get; }
|
||||
|
||||
public IReadOnlyList<string> Assemblies { get; }
|
||||
|
||||
public IReadOnlyList<Dependency> Dependencies { get; }
|
||||
|
||||
public bool Serviceable { get; }
|
||||
}
|
||||
}
|
|
@ -6,12 +6,11 @@
|
|||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>482b1045-a1fa-4063-a0d9-a8107a91a016</ProjectGuid>
|
||||
<ProjectGuid>688870c8-9843-4f9e-8576-d39290ad0f25</ProjectGuid>
|
||||
<RootNamespace>Microsoft.Extensions.DependencyModel</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
|
|
30
src/Microsoft.Extensions.DependencyModel/project.json
Normal file
30
src/Microsoft.Extensions.DependencyModel/project.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"description": "Abstractions for reading `.deps` files.",
|
||||
"version": "1.0.0-*",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/dotnet/cli"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true,
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "7.0.1"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": {},
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"System.Runtime": "4.0.21-rc2-23618",
|
||||
"System.Dynamic.Runtime": "4.0.11-rc2-23616"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"postcompile": [
|
||||
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.dll\"",
|
||||
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.pdb\""
|
||||
]
|
||||
}
|
||||
}
|
|
@ -6,7 +6,8 @@
|
|||
"url": "git://github.com/dotnet/cli"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
"warningsAsErrors": true,
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "7.0.1",
|
||||
|
|
|
@ -10,14 +10,16 @@ include_directories(inc)
|
|||
set(SOURCES
|
||||
src/args.cpp
|
||||
src/main.cpp
|
||||
src/tpafile.cpp
|
||||
src/deps_resolver.cpp
|
||||
src/trace.cpp
|
||||
src/utils.cpp
|
||||
src/coreclr.cpp
|
||||
src/servicing_index.cpp
|
||||
|
||||
inc/args.h
|
||||
inc/pal.h
|
||||
inc/tpafile.h
|
||||
inc/servicing_index.h
|
||||
inc/deps_resolver.h
|
||||
inc/trace.h
|
||||
inc/coreclr.h
|
||||
inc/utils.h)
|
||||
|
|
|
@ -4,14 +4,20 @@
|
|||
#ifndef ARGS_H
|
||||
#define ARGS_H
|
||||
|
||||
#include "utils.h"
|
||||
#include "pal.h"
|
||||
#include "trace.h"
|
||||
|
||||
struct arguments_t
|
||||
{
|
||||
pal::string_t own_path;
|
||||
pal::string_t app_dir;
|
||||
pal::string_t dotnet_servicing;
|
||||
pal::string_t dotnet_runtime_servicing;
|
||||
pal::string_t dotnet_home;
|
||||
pal::string_t dotnet_packages;
|
||||
pal::string_t dotnet_packages_cache;
|
||||
pal::string_t managed_application;
|
||||
pal::string_t clr_path;
|
||||
|
||||
int app_argc;
|
||||
const pal::char_t** app_argv;
|
||||
|
|
102
src/corehost/inc/deps_resolver.h
Normal file
102
src/corehost/inc/deps_resolver.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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.
|
||||
|
||||
#ifndef DEPS_RESOLVER_H
|
||||
#define DEPS_RESOLVER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "pal.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include "servicing_index.h"
|
||||
|
||||
struct deps_entry_t
|
||||
{
|
||||
pal::string_t library_type;
|
||||
pal::string_t library_name;
|
||||
pal::string_t library_version;
|
||||
pal::string_t library_hash;
|
||||
pal::string_t asset_type;
|
||||
pal::string_t asset_name;
|
||||
pal::string_t relative_path;
|
||||
bool is_serviceable;
|
||||
|
||||
// Given a "base" dir, yield the relative path in the package layout.
|
||||
bool to_full_path(const pal::string_t& root, pal::string_t* str) const;
|
||||
|
||||
// Given a "base" dir, yield the relative path in the package layout only if
|
||||
// the hash matches contents of the hash file.
|
||||
bool to_hash_matched_path(const pal::string_t& root, pal::string_t* str) const;
|
||||
};
|
||||
|
||||
// Probe paths to be resolved for ordering
|
||||
struct probe_paths_t
|
||||
{
|
||||
pal::string_t tpa;
|
||||
pal::string_t native;
|
||||
pal::string_t culture;
|
||||
};
|
||||
|
||||
class deps_resolver_t
|
||||
{
|
||||
public:
|
||||
deps_resolver_t(const arguments_t& args)
|
||||
: m_svc(args.dotnet_servicing)
|
||||
{
|
||||
m_deps_valid = parse_deps_file(args);
|
||||
}
|
||||
|
||||
bool valid() { return m_deps_valid; }
|
||||
|
||||
bool resolve_probe_paths(
|
||||
const pal::string_t& app_dir,
|
||||
const pal::string_t& package_dir,
|
||||
const pal::string_t& package_cache_dir,
|
||||
const pal::string_t& clr_dir,
|
||||
probe_paths_t* probe_paths);
|
||||
|
||||
private:
|
||||
|
||||
bool load();
|
||||
|
||||
bool parse_deps_file(const arguments_t& args);
|
||||
|
||||
// Resolve order for TPA lookup.
|
||||
void resolve_tpa_list(
|
||||
const pal::string_t& app_dir,
|
||||
const pal::string_t& package_dir,
|
||||
const pal::string_t& package_cache_dir,
|
||||
const pal::string_t& clr_dir,
|
||||
pal::string_t* output);
|
||||
|
||||
// Resolve order for culture and native DLL lookup.
|
||||
void resolve_probe_dirs(
|
||||
const pal::string_t& asset_type,
|
||||
const pal::string_t& app_dir,
|
||||
const pal::string_t& package_dir,
|
||||
const pal::string_t& package_cache_dir,
|
||||
const pal::string_t& clr_dir,
|
||||
pal::string_t* output);
|
||||
|
||||
// Populate local assemblies from app_dir listing.
|
||||
void get_local_assemblies(const pal::string_t& dir);
|
||||
|
||||
// Servicing index to resolve serviced assembly paths.
|
||||
servicing_index_t m_svc;
|
||||
|
||||
// Map of simple name -> full path of local assemblies populated in priority
|
||||
// order of their extensions.
|
||||
std::unordered_map<pal::string_t, pal::string_t> m_local_assemblies;
|
||||
|
||||
// Entries in the dep file
|
||||
std::vector<deps_entry_t> m_deps_entries;
|
||||
|
||||
// The dep file path
|
||||
pal::string_t m_deps_path;
|
||||
|
||||
// Is the deps file valid
|
||||
bool m_deps_valid;
|
||||
};
|
||||
|
||||
#endif // DEPS_RESOLVER_H
|
|
@ -12,6 +12,9 @@
|
|||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
|
@ -65,7 +68,13 @@ namespace pal
|
|||
typedef wchar_t char_t;
|
||||
typedef std::wstring string_t;
|
||||
typedef std::wstringstream stringstream_t;
|
||||
typedef std::ifstream ifstream_t;
|
||||
// TODO: Agree on the correct encoding of the files: The PoR for now is to
|
||||
// temporarily wchar for Windows and char for Unix. Current implementation
|
||||
// implicitly expects the contents on both Windows and Unix as char and
|
||||
// converts them to wchar in code for Windows. This line should become:
|
||||
// typedef std::basic_ifstream<pal::char_t> ifstream_t.
|
||||
typedef std::basic_ifstream<char> ifstream_t;
|
||||
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
|
||||
typedef HRESULT hresult_t;
|
||||
typedef HMODULE dll_t;
|
||||
typedef FARPROC proc_t;
|
||||
|
@ -77,11 +86,14 @@ namespace pal
|
|||
|
||||
pal::string_t to_palstring(const std::string& str);
|
||||
std::string to_stdstring(const pal::string_t& str);
|
||||
void to_palstring(const char* str, pal::string_t* out);
|
||||
void to_stdstring(const pal::char_t* str, std::string* out);
|
||||
#else
|
||||
typedef char char_t;
|
||||
typedef std::string string_t;
|
||||
typedef std::stringstream stringstream_t;
|
||||
typedef std::ifstream ifstream_t;
|
||||
typedef std::basic_ifstream<char> ifstream_t;
|
||||
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
|
||||
typedef int hresult_t;
|
||||
typedef void* dll_t;
|
||||
typedef void* proc_t;
|
||||
|
@ -92,24 +104,26 @@ namespace pal
|
|||
inline void err_vprintf(const char_t* format, va_list vl) { ::vfprintf(stderr, format, vl); ::fputc('\n', stderr); }
|
||||
inline pal::string_t to_palstring(const std::string& str) { return str; }
|
||||
inline std::string to_stdstring(const pal::string_t& str) { return str; }
|
||||
inline void to_palstring(const char* str, pal::string_t* out) { out->assign(str); }
|
||||
inline void to_stdstring(const pal::char_t* str, std::string* out) { out->assign(str); }
|
||||
#endif
|
||||
|
||||
bool realpath(string_t& path);
|
||||
bool realpath(string_t* path);
|
||||
bool file_exists(const string_t& path);
|
||||
std::vector<pal::string_t> readdir(const string_t& path);
|
||||
inline bool directory_exists(const string_t& path) { return file_exists(path); }
|
||||
void readdir(const string_t& path, std::vector<pal::string_t>* list);
|
||||
|
||||
bool get_own_executable_path(string_t& recv);
|
||||
bool getenv(const char_t* name, string_t& recv);
|
||||
bool get_default_packages_directory(string_t& recv);
|
||||
bool get_own_executable_path(string_t* recv);
|
||||
bool getenv(const char_t* name, string_t* recv);
|
||||
bool get_default_packages_directory(string_t* recv);
|
||||
bool is_path_rooted(const string_t& path);
|
||||
|
||||
int xtoi(const char_t* input);
|
||||
|
||||
bool load_library(const char_t* path, dll_t& dll);
|
||||
bool load_library(const char_t* path, dll_t* dll);
|
||||
proc_t get_symbol(dll_t library, const char* name);
|
||||
void unload_library(dll_t library);
|
||||
|
||||
bool find_coreclr(pal::string_t& recv);
|
||||
bool find_coreclr(pal::string_t* recv);
|
||||
}
|
||||
|
||||
#endif // PAL_H
|
||||
|
|
24
src/corehost/inc/servicing_index.h
Normal file
24
src/corehost/inc/servicing_index.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
// 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.
|
||||
|
||||
#include "utils.h"
|
||||
#include "args.h"
|
||||
|
||||
class servicing_index_t
|
||||
{
|
||||
public:
|
||||
servicing_index_t(const pal::string_t& svc_dir);
|
||||
|
||||
bool find_redirection(const pal::string_t& package_name,
|
||||
const pal::string_t& package_version,
|
||||
const pal::string_t& package_relative,
|
||||
pal::string_t* redirection);
|
||||
|
||||
private:
|
||||
void ensure_redirections();
|
||||
|
||||
std::unordered_map<pal::string_t, pal::string_t> m_redirections;
|
||||
pal::string_t m_patch_root;
|
||||
pal::string_t m_index_file;
|
||||
bool m_parsed;
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
// 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.
|
||||
|
||||
#ifndef TPAFILE_H
|
||||
#define TPAFILE_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "pal.h"
|
||||
#include "trace.h"
|
||||
|
||||
struct tpaentry_t
|
||||
{
|
||||
pal::string_t library_type;
|
||||
pal::string_t library_name;
|
||||
pal::string_t library_version;
|
||||
pal::string_t library_hash;
|
||||
pal::string_t asset_type;
|
||||
pal::string_t asset_name;
|
||||
pal::string_t relative_path;
|
||||
};
|
||||
|
||||
class tpafile
|
||||
{
|
||||
public:
|
||||
bool load(pal::string_t path);
|
||||
|
||||
void add_from_local_dir(const pal::string_t& dir);
|
||||
void add_package_dir(pal::string_t dir);
|
||||
void add_native_search_path(pal::string_t dir);
|
||||
|
||||
void write_tpa_list(pal::string_t& output);
|
||||
void write_native_paths(pal::string_t& output);
|
||||
|
||||
private:
|
||||
std::vector<tpaentry_t> m_entries;
|
||||
std::vector<pal::string_t> m_native_search_paths;
|
||||
std::vector<pal::string_t> m_package_search_paths;
|
||||
};
|
||||
|
||||
#endif // TPAFILE_H
|
|
@ -10,7 +10,7 @@ bool ends_with(const pal::string_t& value, const pal::string_t& suffix);
|
|||
pal::string_t get_executable(const pal::string_t& filename);
|
||||
pal::string_t get_directory(const pal::string_t& path);
|
||||
pal::string_t get_filename(const pal::string_t& path);
|
||||
void append_path(pal::string_t& path1, const pal::char_t* path2);
|
||||
void append_path(pal::string_t* path1, const pal::char_t* path2);
|
||||
bool coreclr_exists_in_dir(const pal::string_t& candidate);
|
||||
|
||||
void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl);
|
||||
#endif
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#include "args.h"
|
||||
#include "utils.h"
|
||||
#include "coreclr.h"
|
||||
|
||||
arguments_t::arguments_t() :
|
||||
managed_application(_X("")),
|
||||
clr_path(_X("")),
|
||||
own_path(_X("")),
|
||||
app_dir(_X("")),
|
||||
app_argc(0),
|
||||
app_argv(nullptr)
|
||||
{
|
||||
|
@ -24,10 +26,22 @@ void display_help()
|
|||
|
||||
bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args)
|
||||
{
|
||||
// Get the full name of the application
|
||||
if (!pal::get_own_executable_path(args.own_path) || !pal::realpath(args.own_path))
|
||||
// Read trace environment variable
|
||||
pal::string_t trace_str;
|
||||
if (pal::getenv(_X("COREHOST_TRACE"), &trace_str))
|
||||
{
|
||||
trace::error(_X("failed to locate current executable"));
|
||||
auto trace_val = pal::xtoi(trace_str.c_str());
|
||||
if (trace_val > 0)
|
||||
{
|
||||
trace::enable();
|
||||
trace::info(_X("Tracing enabled"));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the full name of the application
|
||||
if (!pal::get_own_executable_path(&args.own_path) || !pal::realpath(&args.own_path))
|
||||
{
|
||||
trace::error(_X("Failed to locate current executable"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -43,6 +57,12 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg
|
|||
return false;
|
||||
}
|
||||
args.managed_application = pal::string_t(argv[1]);
|
||||
if (!pal::realpath(&args.managed_application))
|
||||
{
|
||||
trace::error(_X("Failed to locate managed application: %s"), args.managed_application.c_str());
|
||||
return false;
|
||||
}
|
||||
args.app_dir = get_directory(args.managed_application);
|
||||
args.app_argc = argc - 2;
|
||||
args.app_argv = &argv[2];
|
||||
}
|
||||
|
@ -54,39 +74,20 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg
|
|||
managed_app.append(get_executable(own_name));
|
||||
managed_app.append(_X(".dll"));
|
||||
args.managed_application = managed_app;
|
||||
if (!pal::realpath(&args.managed_application))
|
||||
{
|
||||
trace::error(_X("Failed to locate managed application: %s"), args.managed_application.c_str());
|
||||
return false;
|
||||
}
|
||||
args.app_dir = own_dir;
|
||||
args.app_argv = &argv[1];
|
||||
args.app_argc = argc - 1;
|
||||
}
|
||||
|
||||
// Read trace environment variable
|
||||
pal::string_t trace_str;
|
||||
if (pal::getenv(_X("COREHOST_TRACE"), trace_str))
|
||||
{
|
||||
auto trace_val = pal::xtoi(trace_str.c_str());
|
||||
if (trace_val > 0)
|
||||
{
|
||||
trace::enable();
|
||||
trace::info(_X("tracing enabled"));
|
||||
}
|
||||
}
|
||||
|
||||
// Read CLR path from environment variable
|
||||
pal::string_t home_str;
|
||||
pal::getenv(_X("DOTNET_HOME"), home_str);
|
||||
if (!home_str.empty())
|
||||
{
|
||||
append_path(home_str, _X("runtime"));
|
||||
append_path(home_str, _X("coreclr"));
|
||||
args.clr_path.assign(home_str);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use platform-specific search algorithm
|
||||
if (pal::find_coreclr(home_str))
|
||||
{
|
||||
args.clr_path.assign(home_str);
|
||||
}
|
||||
}
|
||||
|
||||
pal::getenv(_X("DOTNET_PACKAGES"), &args.dotnet_packages);
|
||||
pal::getenv(_X("DOTNET_PACKAGES_CACHE"), &args.dotnet_packages_cache);
|
||||
pal::getenv(_X("DOTNET_SERVICING"), &args.dotnet_servicing);
|
||||
pal::getenv(_X("DOTNET_RUNTIME_SERVICING"), &args.dotnet_runtime_servicing);
|
||||
pal::getenv(_X("DOTNET_HOME"), &args.dotnet_home);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -41,9 +41,9 @@ bool coreclr::bind(const pal::string_t& libcoreclr_path)
|
|||
assert(g_coreclr == nullptr);
|
||||
|
||||
pal::string_t coreclr_dll_path(libcoreclr_path);
|
||||
append_path(coreclr_dll_path, LIBCORECLR_NAME);
|
||||
append_path(&coreclr_dll_path, LIBCORECLR_NAME);
|
||||
|
||||
if (!pal::load_library(coreclr_dll_path.c_str(), g_coreclr))
|
||||
if (!pal::load_library(coreclr_dll_path.c_str(), &g_coreclr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
597
src/corehost/src/deps_resolver.cpp
Normal file
597
src/corehost/src/deps_resolver.cpp
Normal file
|
@ -0,0 +1,597 @@
|
|||
// 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.
|
||||
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include <cassert>
|
||||
|
||||
#include "trace.h"
|
||||
#include "deps_resolver.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// -----------------------------------------------------------------------------
|
||||
// Read a single field from the deps entry
|
||||
//
|
||||
// Parameters:
|
||||
// line - A deps file entry line
|
||||
// buf - The temporary buffer to use while parsing (with size to contain "line")
|
||||
// ofs - The offset that this method will read from "line" on invocation and
|
||||
// the offset that has been consumed by this method upon successful exit
|
||||
// field - The current field read from the line
|
||||
//
|
||||
// Assumption:
|
||||
// The line should be in a CSV format, with commas separating the fields.
|
||||
// The fields themselves will be quoted. The escape character is '\\'
|
||||
//
|
||||
// Returns:
|
||||
// True if parsed successfully. Else, false
|
||||
//
|
||||
// Note:
|
||||
// Callers cannot call with the same "line" upon an unsuccessful exit.
|
||||
bool read_field(const pal::string_t& line, pal::char_t* buf, unsigned* ofs, pal::string_t* field)
|
||||
{
|
||||
unsigned& offset = *ofs;
|
||||
pal::string_t& value_recv = *field;
|
||||
|
||||
// The first character should be a '"'
|
||||
if (line[offset] != '"')
|
||||
{
|
||||
trace::error(_X("Error reading TPA file"));
|
||||
return false;
|
||||
}
|
||||
offset++;
|
||||
|
||||
auto buf_offset = 0;
|
||||
|
||||
// Iterate through characters in the string
|
||||
for (; offset < line.length(); offset++)
|
||||
{
|
||||
// Is this a '\'?
|
||||
if (line[offset] == '\\')
|
||||
{
|
||||
// Skip this character and read the next character into the buffer
|
||||
offset++;
|
||||
buf[buf_offset] = line[offset];
|
||||
}
|
||||
// Is this a '"'?
|
||||
else if (line[offset] == '\"')
|
||||
{
|
||||
// Done! Advance to the pointer after the input
|
||||
offset++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Take the character
|
||||
buf[buf_offset] = line[offset];
|
||||
}
|
||||
buf_offset++;
|
||||
}
|
||||
buf[buf_offset] = '\0';
|
||||
value_recv.assign(buf);
|
||||
|
||||
// Consume the ',' if we have one
|
||||
if (line[offset] == ',')
|
||||
{
|
||||
offset++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// A uniqifying append helper that doesn't let two entries with the same
|
||||
// "asset_name" be part of the "output" paths.
|
||||
//
|
||||
void add_tpa_asset(
|
||||
const pal::string_t& asset_name,
|
||||
const pal::string_t& asset_path,
|
||||
std::set<pal::string_t>* items,
|
||||
pal::string_t* output)
|
||||
{
|
||||
if (items->count(asset_name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
trace::verbose(_X("Adding tpa entry: %s"), asset_path.c_str());
|
||||
output->append(asset_path);
|
||||
output->push_back(PATH_SEPARATOR);
|
||||
items->insert(asset_name);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Add mscorlib from the CLR directory. Even if CLR is serviced, we should pick
|
||||
// mscorlib from the CLR directory. If mscorlib could not be found in the CLR
|
||||
// location, then leave it to the CLR to pick the right mscorlib.
|
||||
//
|
||||
void add_mscorlib_to_tpa(const pal::string_t& clr_dir, std::set<pal::string_t>* items, pal::string_t* output)
|
||||
{
|
||||
pal::string_t mscorlib_ni_path = clr_dir + DIR_SEPARATOR + _X("mscorlib.ni.dll");
|
||||
if (pal::file_exists(mscorlib_ni_path))
|
||||
{
|
||||
add_tpa_asset(_X("mscorlib"), mscorlib_ni_path, items, output);
|
||||
return;
|
||||
}
|
||||
|
||||
pal::string_t mscorlib_path = clr_dir + DIR_SEPARATOR + _X("mscorlib.dll");
|
||||
if (pal::file_exists(mscorlib_path))
|
||||
{
|
||||
add_tpa_asset(_X("mscorlib"), mscorlib_ni_path, items, output);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// A uniqifying append helper that doesn't let two "paths" to be identical in
|
||||
// the "output" string.
|
||||
//
|
||||
void add_unique_path(
|
||||
const pal::string_t& type,
|
||||
const pal::string_t& path,
|
||||
std::set<pal::string_t>* existing,
|
||||
pal::string_t* output)
|
||||
{
|
||||
if (existing->count(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
trace::verbose(_X("Adding to %s path: %s"), type.c_str(), path.c_str());
|
||||
output->append(path);
|
||||
output->push_back(PATH_SEPARATOR);
|
||||
existing->insert(path);
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Given a "base" directory, yield the relative path of this file in the package
|
||||
// layout.
|
||||
//
|
||||
// Parameters:
|
||||
// base - The base directory to look for the relative path of this entry
|
||||
// str - If the method returns true, contains the file path for this deps
|
||||
// entry relative to the "base" directory
|
||||
//
|
||||
// Returns:
|
||||
// If the file exists in the path relative to the "base" directory.
|
||||
//
|
||||
bool deps_entry_t::to_full_path(const pal::string_t& base, pal::string_t* str) const
|
||||
{
|
||||
pal::string_t& candidate = *str;
|
||||
|
||||
candidate.clear();
|
||||
|
||||
// Entry relative path contains '/' separator, sanitize it to use
|
||||
// platform separator. Perf: avoid extra copy if it matters.
|
||||
pal::string_t pal_relative_path = relative_path;
|
||||
if (_X('/') != DIR_SEPARATOR)
|
||||
{
|
||||
replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR);
|
||||
}
|
||||
|
||||
// Reserve space for the path below
|
||||
candidate.reserve(base.length() +
|
||||
library_name.length() +
|
||||
library_version.length() +
|
||||
pal_relative_path.length() + 3);
|
||||
|
||||
candidate.assign(base);
|
||||
append_path(&candidate, library_name.c_str());
|
||||
append_path(&candidate, library_version.c_str());
|
||||
append_path(&candidate, pal_relative_path.c_str());
|
||||
|
||||
bool exists = pal::file_exists(candidate);
|
||||
if (!exists)
|
||||
{
|
||||
candidate.clear();
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Given a "base" directory, yield the relative path of this file in the package
|
||||
// layout if the entry hash matches the hash file in the "base" directory
|
||||
//
|
||||
// Parameters:
|
||||
// base - The base directory to look for the relative path of this entry and
|
||||
// the hash file.
|
||||
// str - If the method returns true, contains the file path for this deps
|
||||
// entry relative to the "base" directory
|
||||
//
|
||||
// Description:
|
||||
// Looks for a file named "{PackageName}.{PackageVersion}.nupkg.{HashAlgorithm}"
|
||||
// If the deps entry's {HashAlgorithm}-{HashValue} matches the contents then
|
||||
// yields the relative path of this entry in the "base" dir.
|
||||
//
|
||||
// Returns:
|
||||
// If the file exists in the path relative to the "base" directory and there
|
||||
// was hash file match with this deps entry.
|
||||
//
|
||||
// See: to_full_path(base, str)
|
||||
//
|
||||
bool deps_entry_t::to_hash_matched_path(const pal::string_t& base, pal::string_t* str) const
|
||||
{
|
||||
pal::string_t& candidate = *str;
|
||||
|
||||
candidate.clear();
|
||||
|
||||
// Base directory must be present to perform hash lookup.
|
||||
if (base.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// First detect position of hyphen in [Algorithm]-[Hash] in the string.
|
||||
size_t pos = library_hash.find(_X("-"));
|
||||
if (pos == 0 || pos == pal::string_t::npos)
|
||||
{
|
||||
trace::verbose(_X("Invalid hash %s value for deps file entry: %s"), library_hash.c_str(), library_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build the nupkg file name. Just reserve approx 8 char_t's for the algorithm name.
|
||||
pal::string_t nupkg_filename;
|
||||
nupkg_filename.reserve(library_name.length() + 1 + library_version.length() + 16);
|
||||
nupkg_filename.append(library_name);
|
||||
nupkg_filename.append(_X("."));
|
||||
nupkg_filename.append(library_version);
|
||||
nupkg_filename.append(_X(".nupkg."));
|
||||
nupkg_filename.append(library_hash.substr(0, pos));
|
||||
|
||||
// Build the hash file path str.
|
||||
pal::string_t hash_file;
|
||||
hash_file.reserve(base.length() + library_name.length() + library_version.length() + nupkg_filename.length() + 3);
|
||||
hash_file.assign(base);
|
||||
append_path(&hash_file, library_name.c_str());
|
||||
append_path(&hash_file, library_version.c_str());
|
||||
append_path(&hash_file, nupkg_filename.c_str());
|
||||
|
||||
// Read the contents of the hash file.
|
||||
pal::ifstream_t fstream(hash_file);
|
||||
if (!fstream.good())
|
||||
{
|
||||
trace::verbose(_X("The hash file is invalid [%s]"), hash_file.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtain the hash from the file.
|
||||
std::string hash;
|
||||
hash.assign(pal::istreambuf_iterator_t(fstream),
|
||||
pal::istreambuf_iterator_t());
|
||||
pal::string_t pal_hash;
|
||||
pal::to_palstring(hash.c_str(), &pal_hash);
|
||||
|
||||
// Check if contents match deps entry.
|
||||
pal::string_t entry_hash = library_hash.substr(pos + 1);
|
||||
if (entry_hash != pal_hash)
|
||||
{
|
||||
trace::verbose(_X("The file hash [%s][%d] did not match entry hash [%s][%d]"),
|
||||
pal_hash.c_str(), pal_hash.length(), entry_hash.c_str(), entry_hash.length());
|
||||
return false;
|
||||
}
|
||||
|
||||
// All good, just append the relative dir to base.
|
||||
return to_full_path(base, &candidate);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Load the deps file and parse its "entry" lines which contain the "fields" of
|
||||
// the entry. Populate an array of these entries.
|
||||
//
|
||||
bool deps_resolver_t::load()
|
||||
{
|
||||
// If file doesn't exist, then assume parsed.
|
||||
if (!pal::file_exists(m_deps_path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Somehow the file stream could not be opened. This is an error.
|
||||
pal::ifstream_t file(m_deps_path);
|
||||
if (!file.good())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the "entry" lines of the deps file.
|
||||
std::string stdline;
|
||||
while (std::getline(file, stdline))
|
||||
{
|
||||
pal::string_t line;
|
||||
pal::to_palstring(stdline.c_str(), &line);
|
||||
|
||||
deps_entry_t entry;
|
||||
pal::string_t is_serviceable;
|
||||
pal::string_t* fields[] = {
|
||||
&entry.library_type,
|
||||
&entry.library_name,
|
||||
&entry.library_version,
|
||||
&entry.library_hash,
|
||||
&entry.asset_type,
|
||||
&entry.asset_name,
|
||||
&entry.relative_path,
|
||||
// TODO: Add when the deps file support is enabled.
|
||||
// &is_serviceable
|
||||
};
|
||||
|
||||
std::vector<pal::char_t> buf(line.length());
|
||||
|
||||
for (unsigned i = 0, offset = 0; i < sizeof(fields) / sizeof(fields[0]); ++i)
|
||||
{
|
||||
if (!(read_field(line, buf.data(), &offset, fields[i])))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Serviceable, if not false, default is true.
|
||||
entry.is_serviceable = pal::strcasecmp(is_serviceable.c_str(), _X("false")) != 0;
|
||||
|
||||
// TODO: Deps file does not follow spec. It uses '\\', should use '/'
|
||||
replace_char(&entry.relative_path, _X('\\'), _X('/'));
|
||||
|
||||
m_deps_entries.push_back(entry);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Resolve path to the deps file from "args" and parse the deps file.
|
||||
//
|
||||
// Returns:
|
||||
// True if the file parse is successful or if file doesn't exist. False,
|
||||
// when there is an error parsing the file.
|
||||
//
|
||||
bool deps_resolver_t::parse_deps_file(const arguments_t& args)
|
||||
{
|
||||
const auto& app_base = args.app_dir;
|
||||
auto app_name = get_filename(args.managed_application);
|
||||
|
||||
m_deps_path.reserve(app_base.length() + 1 + app_name.length() + 5);
|
||||
m_deps_path.append(app_base);
|
||||
m_deps_path.push_back(DIR_SEPARATOR);
|
||||
m_deps_path.append(app_name, 0, app_name.find_last_of(_X(".")));
|
||||
m_deps_path.append(_X(".deps"));
|
||||
|
||||
return load();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Load local assemblies by priority order of their file extensions and
|
||||
// unique-fied by their simple name.
|
||||
//
|
||||
void deps_resolver_t::get_local_assemblies(const pal::string_t& dir)
|
||||
{
|
||||
trace::verbose(_X("Adding files from dir %s"), dir.c_str());
|
||||
|
||||
// Managed extensions in priority order, pick DLL over EXE and NI over IL.
|
||||
const pal::string_t managed_ext[] = { _X(".ni.dll"), _X(".dll"), _X(".ni.exe"), _X(".exe") };
|
||||
|
||||
// List of files in the dir
|
||||
std::vector<pal::string_t> files;
|
||||
pal::readdir(dir, &files);
|
||||
|
||||
for (const auto& file : files)
|
||||
{
|
||||
for (const auto& ext : managed_ext)
|
||||
{
|
||||
// Nothing to do if file length is smaller than expected ext.
|
||||
if (file.length() <= ext.length())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto file_name = file.substr(0, file.length() - ext.length());
|
||||
auto file_ext = file.substr(file_name.length());
|
||||
|
||||
// Ext did not match expected ext, skip this file.
|
||||
if (pal::strcasecmp(file_ext.c_str(), ext.c_str()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Do a case insensitive lookup.
|
||||
// Already added entry for this asset, by priority order skip this ext
|
||||
if (m_local_assemblies.count(file_name))
|
||||
{
|
||||
trace::verbose(_X("Skipping %s because the %s already exists in local assemblies"), file.c_str(), m_local_assemblies.find(file_name)->second.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add entry for this asset
|
||||
pal::string_t file_path = dir + DIR_SEPARATOR + file;
|
||||
trace::verbose(_X("Adding %s to local assembly set from %s"), file_name.c_str(), file_path.c_str());
|
||||
m_local_assemblies.emplace(file_name, file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Resolve the TPA list order.
|
||||
//
|
||||
// Description:
|
||||
// First, add mscorlib to the TPA. Then for each deps entry, check if they
|
||||
// are serviced. If they are not serviced, then look if they are present
|
||||
// app local. Worst case, default to the primary and seconday package
|
||||
// caches. Finally, for cases where deps file may not be present or if deps
|
||||
// did not have an entry for an app local assembly, just use them from the
|
||||
// app dir in the TPA path.
|
||||
//
|
||||
// Parameters:
|
||||
// app_dir - The application local directory
|
||||
// package_dir - The directory path to where packages are restored
|
||||
// package_cache_dir - The directory path to secondary cache for packages
|
||||
// clr_dir - The directory where the host loads the CLR
|
||||
//
|
||||
// Returns:
|
||||
// output - Pointer to a string that will hold the resolved TPA paths
|
||||
//
|
||||
void deps_resolver_t::resolve_tpa_list(
|
||||
const pal::string_t& app_dir,
|
||||
const pal::string_t& package_dir,
|
||||
const pal::string_t& package_cache_dir,
|
||||
const pal::string_t& clr_dir,
|
||||
pal::string_t* output)
|
||||
{
|
||||
// Obtain the local assemblies in the app dir.
|
||||
get_local_assemblies(app_dir);
|
||||
|
||||
std::set<pal::string_t> items;
|
||||
|
||||
add_mscorlib_to_tpa(clr_dir, &items, output);
|
||||
|
||||
for (const deps_entry_t& entry : m_deps_entries)
|
||||
{
|
||||
// Is this asset a "runtime" type?
|
||||
if (entry.asset_type != _X("runtime") || items.count(entry.asset_name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pal::string_t candidate;
|
||||
|
||||
// Is this a serviceable entry and is there an entry in the servicing index?
|
||||
if (entry.is_serviceable && entry.library_type == _X("Package") &&
|
||||
m_svc.find_redirection(entry.library_name, entry.library_version, entry.relative_path, &candidate))
|
||||
{
|
||||
add_tpa_asset(entry.asset_name, candidate, &items, output);
|
||||
}
|
||||
// Is this entry present locally?
|
||||
else if (m_local_assemblies.count(entry.asset_name))
|
||||
{
|
||||
// TODO: Case insensitive look up?
|
||||
add_tpa_asset(entry.asset_name, m_local_assemblies.find(entry.asset_name)->second, &items, output);
|
||||
}
|
||||
// Is this entry present in the package restore dir?
|
||||
else if (entry.to_full_path(package_dir, &candidate))
|
||||
{
|
||||
add_tpa_asset(entry.asset_name, candidate, &items, output);
|
||||
}
|
||||
// Is this entry present in the secondary package cache?
|
||||
else if (entry.to_hash_matched_path(package_cache_dir, &candidate))
|
||||
{
|
||||
add_tpa_asset(entry.asset_name, candidate, &items, output);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, if the deps file wasn't present or has missing entries, then
|
||||
// add the app local assemblies to the TPA.
|
||||
for (const auto& kv : m_local_assemblies)
|
||||
{
|
||||
add_tpa_asset(kv.first, kv.second, &items, output);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Resolve the directories order for culture/native lookup
|
||||
//
|
||||
// Description:
|
||||
// This general purpose function specifies priority order of directory lookup
|
||||
// for both native images and culture specific resource images. Lookup for
|
||||
// culture assemblies is done by looking up two levels above from the file
|
||||
// path. Lookup for native images is done by looking up one level from the
|
||||
// file path.
|
||||
//
|
||||
// Parameters:
|
||||
// asset_type - The type of the asset that needs lookup, currently
|
||||
// supports "culture" and "native"
|
||||
// app_dir - The application local directory
|
||||
// package_dir - The directory path to where packages are restored
|
||||
// package_cache_dir - The directory path to secondary cache for packages
|
||||
// clr_dir - The directory where the host loads the CLR
|
||||
//
|
||||
// Returns:
|
||||
// output - Pointer to a string that will hold the resolved lookup dirs
|
||||
//
|
||||
void deps_resolver_t::resolve_probe_dirs(
|
||||
const pal::string_t& asset_type,
|
||||
const pal::string_t& app_dir,
|
||||
const pal::string_t& package_dir,
|
||||
const pal::string_t& package_cache_dir,
|
||||
const pal::string_t& clr_dir,
|
||||
pal::string_t* output)
|
||||
{
|
||||
assert(asset_type == _X("culture") || asset_type == _X("native"));
|
||||
|
||||
// For culture assemblies, we need to provide the base directory of the culture path.
|
||||
// For example: .../Foo/en-US/Bar.dll, then, the resolved path is .../Foo
|
||||
std::function<pal::string_t(const pal::string_t&)> culture = [] (const pal::string_t& str) {
|
||||
return get_directory(get_directory(str));
|
||||
};
|
||||
// For native assemblies, obtain the directory path from the file path
|
||||
std::function<pal::string_t(const pal::string_t&)> native = [] (const pal::string_t& str) {
|
||||
return get_directory(str);
|
||||
};
|
||||
std::function<pal::string_t(const pal::string_t&)>& action = (asset_type == _X("culture")) ? culture : native;
|
||||
|
||||
std::set<pal::string_t> items;
|
||||
|
||||
// Fill the "output" with serviced DLL directories if they are serviceable
|
||||
// and have an entry present.
|
||||
for (const deps_entry_t& entry : m_deps_entries)
|
||||
{
|
||||
pal::string_t redirection_path;
|
||||
if (entry.is_serviceable && entry.asset_type == asset_type && entry.library_type == _X("Package") &&
|
||||
m_svc.find_redirection(entry.library_name, entry.library_version, entry.relative_path, &redirection_path))
|
||||
{
|
||||
add_unique_path(asset_type, action(redirection_path), &items, output);
|
||||
}
|
||||
}
|
||||
|
||||
// App local path
|
||||
add_unique_path(asset_type, app_dir, &items, output);
|
||||
|
||||
// Take care of the packages cached path
|
||||
for (const deps_entry_t& entry : m_deps_entries)
|
||||
{
|
||||
if (entry.asset_type != asset_type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pal::string_t candidate;
|
||||
// Package restore directory
|
||||
if (entry.to_full_path(package_dir, &candidate))
|
||||
{
|
||||
add_unique_path(asset_type, action(candidate), &items, output);
|
||||
}
|
||||
// Secondary cache
|
||||
else if (entry.to_hash_matched_path(package_cache_dir, &candidate))
|
||||
{
|
||||
add_unique_path(asset_type, action(candidate), &items, output);
|
||||
}
|
||||
}
|
||||
|
||||
// CLR path
|
||||
add_unique_path(asset_type, clr_dir, &items, output);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Entrypoint to resolve TPA, native and culture path ordering to pass to CoreCLR.
|
||||
//
|
||||
// Parameters:
|
||||
// app_dir - The application local directory
|
||||
// package_dir - The directory path to where packages are restored
|
||||
// package_cache_dir - The directory path to secondary cache for packages
|
||||
// clr_dir - The directory where the host loads the CLR
|
||||
// probe_paths - Pointer to struct containing fields that will contain
|
||||
// resolved path ordering.
|
||||
//
|
||||
//
|
||||
bool deps_resolver_t::resolve_probe_paths(
|
||||
const pal::string_t& app_dir,
|
||||
const pal::string_t& package_dir,
|
||||
const pal::string_t& package_cache_dir,
|
||||
const pal::string_t& clr_dir,
|
||||
probe_paths_t* probe_paths)
|
||||
{
|
||||
resolve_tpa_list(app_dir, package_dir, package_cache_dir, clr_dir, &probe_paths->tpa);
|
||||
resolve_probe_dirs(_X("native"), app_dir, package_dir, package_cache_dir, clr_dir, &probe_paths->native);
|
||||
resolve_probe_dirs(_X("culture"), app_dir, package_dir, package_cache_dir, clr_dir, &probe_paths->culture);
|
||||
return true;
|
||||
}
|
|
@ -4,121 +4,191 @@
|
|||
#include "pal.h"
|
||||
#include "args.h"
|
||||
#include "trace.h"
|
||||
#include "tpafile.h"
|
||||
#include "deps_resolver.h"
|
||||
#include "utils.h"
|
||||
#include "coreclr.h"
|
||||
|
||||
void get_tpafile_path(const pal::string_t& app_base, const pal::string_t& app_name, pal::string_t& tpapath)
|
||||
enum StatusCode
|
||||
{
|
||||
tpapath.reserve(app_base.length() + app_name.length() + 5);
|
||||
Failure = 0x87FF0000,
|
||||
InvalidArgFailure = Failure | 0x1,
|
||||
CoreClrResolveFailure = Failure | 0x2,
|
||||
CoreClrBindFailure = Failure | 0x3,
|
||||
CoreClrInitFailure = Failure | 0x4,
|
||||
CoreClrExeFailure = Failure | 0x5,
|
||||
ResolverInitFailure = Failure | 0x6,
|
||||
ResolverResolveFailure = Failure | 0x7,
|
||||
};
|
||||
|
||||
tpapath.append(app_base);
|
||||
tpapath.push_back(DIR_SEPARATOR);
|
||||
// ----------------------------------------------------------------------
|
||||
// resolve_clr_path: Resolve CLR Path in priority order
|
||||
//
|
||||
// Description:
|
||||
// Check if CoreCLR library exists in runtime servicing dir or app
|
||||
// local or DOTNET_HOME directory in that order of priority. If these
|
||||
// fail to locate CoreCLR, then check platform-specific search.
|
||||
//
|
||||
// Returns:
|
||||
// "true" if path to the CoreCLR dir can be resolved in "clr_path"
|
||||
// parameter. Else, returns "false" with "clr_path" unmodified.
|
||||
//
|
||||
bool resolve_clr_path(const arguments_t& args, pal::string_t* clr_path)
|
||||
{
|
||||
const pal::string_t* dirs[] = {
|
||||
&args.dotnet_runtime_servicing, // DOTNET_RUNTIME_SERVICING
|
||||
&args.app_dir, // APP LOCAL
|
||||
&args.dotnet_home // DOTNET_HOME
|
||||
};
|
||||
for (int i = 0; i < sizeof(dirs) / sizeof(dirs[0]); ++i)
|
||||
{
|
||||
if (dirs[i]->empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the extension from the app_name
|
||||
auto ext_location = app_name.find_last_of('.');
|
||||
if (ext_location != std::string::npos)
|
||||
{
|
||||
tpapath.append(app_name.substr(0, ext_location));
|
||||
// App dir should contain coreclr, so skip appending path.
|
||||
pal::string_t cur_dir = *dirs[i];
|
||||
if (dirs[i] != &args.app_dir)
|
||||
{
|
||||
append_path(&cur_dir, _X("runtime"));
|
||||
append_path(&cur_dir, _X("coreclr"));
|
||||
}
|
||||
|
||||
// Found coreclr in priority order.
|
||||
if (coreclr_exists_in_dir(cur_dir))
|
||||
{
|
||||
clr_path->assign(cur_dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// Use platform-specific search algorithm
|
||||
pal::string_t home_dir = args.dotnet_home;
|
||||
if (pal::find_coreclr(&home_dir))
|
||||
{
|
||||
tpapath.append(app_name);
|
||||
clr_path->assign(home_dir);
|
||||
return true;
|
||||
}
|
||||
tpapath.append(_X(".deps"));
|
||||
return false;
|
||||
}
|
||||
|
||||
int run(arguments_t args, pal::string_t app_base, tpafile tpa)
|
||||
int run(const arguments_t& args, const pal::string_t& clr_path)
|
||||
{
|
||||
tpa.add_from_local_dir(app_base);
|
||||
// Load the deps resolver
|
||||
deps_resolver_t resolver(args);
|
||||
if (!resolver.valid())
|
||||
{
|
||||
trace::error(_X("Invalid .deps file"));
|
||||
return StatusCode::ResolverInitFailure;
|
||||
}
|
||||
|
||||
// Add packages directory
|
||||
pal::string_t packages_dir;
|
||||
if (!pal::get_default_packages_directory(packages_dir))
|
||||
pal::string_t packages_dir = args.dotnet_packages;
|
||||
if (!pal::directory_exists(packages_dir))
|
||||
{
|
||||
trace::info(_X("did not find local packages directory"));
|
||||
|
||||
// We can continue, the app may have it's dependencies locally
|
||||
(void)pal::get_default_packages_directory(&packages_dir);
|
||||
}
|
||||
else
|
||||
trace::info(_X("Package directory: %s"), packages_dir.empty() ? _X("not specified") : packages_dir.c_str());
|
||||
|
||||
probe_paths_t probe_paths;
|
||||
if (!resolver.resolve_probe_paths(args.app_dir, packages_dir, args.dotnet_packages_cache, clr_path, &probe_paths))
|
||||
{
|
||||
trace::info(_X("using packages directory: %s"), packages_dir.c_str());
|
||||
tpa.add_package_dir(packages_dir);
|
||||
return StatusCode::ResolverResolveFailure;
|
||||
}
|
||||
|
||||
// Add native search path
|
||||
trace::info(_X("using native search path: %s"), packages_dir.c_str());
|
||||
tpa.add_native_search_path(args.clr_path);
|
||||
|
||||
// Build TPA list and search paths
|
||||
pal::string_t tpalist;
|
||||
tpa.write_tpa_list(tpalist);
|
||||
|
||||
pal::string_t search_paths;
|
||||
tpa.write_native_paths(search_paths);
|
||||
|
||||
// Build CoreCLR properties
|
||||
const char* property_keys[] = {
|
||||
"TRUSTED_PLATFORM_ASSEMBLIES",
|
||||
"APP_PATHS",
|
||||
"APP_NI_PATHS",
|
||||
"NATIVE_DLL_SEARCH_DIRECTORIES",
|
||||
"AppDomainCompatSwitch"
|
||||
"PLATFORM_RESOURCE_ROOTS",
|
||||
"AppDomainCompatSwitch",
|
||||
// TODO: pipe this from corehost.json
|
||||
"SERVER_GC"
|
||||
};
|
||||
|
||||
auto tpa_cstr = pal::to_stdstring(tpalist);
|
||||
auto app_base_cstr = pal::to_stdstring(app_base);
|
||||
auto search_paths_cstr = pal::to_stdstring(search_paths);
|
||||
auto tpa_paths_cstr = pal::to_stdstring(probe_paths.tpa);
|
||||
auto app_base_cstr = pal::to_stdstring(args.app_dir);
|
||||
auto native_dirs_cstr = pal::to_stdstring(probe_paths.native);
|
||||
auto culture_dirs_cstr = pal::to_stdstring(probe_paths.culture);
|
||||
|
||||
const char* property_values[] = {
|
||||
// TRUSTED_PLATFORM_ASSEMBLIES
|
||||
tpa_cstr.c_str(),
|
||||
tpa_paths_cstr.c_str(),
|
||||
// APP_PATHS
|
||||
app_base_cstr.c_str(),
|
||||
// APP_NI_PATHS
|
||||
app_base_cstr.c_str(),
|
||||
// NATIVE_DLL_SEARCH_DIRECTORIES
|
||||
search_paths_cstr.c_str(),
|
||||
native_dirs_cstr.c_str(),
|
||||
// PLATFORM_RESOURCE_ROOTS
|
||||
culture_dirs_cstr.c_str(),
|
||||
// AppDomainCompatSwitch
|
||||
"UseLatestBehaviorWhenTFMNotSpecified"
|
||||
"UseLatestBehaviorWhenTFMNotSpecified",
|
||||
// SERVER_GC
|
||||
"1"
|
||||
};
|
||||
|
||||
// Dump TPA list
|
||||
trace::verbose(_X("TPA List: %s"), tpalist.c_str());
|
||||
|
||||
// Dump native search paths
|
||||
trace::verbose(_X("Native Paths: %s"), search_paths.c_str());
|
||||
size_t property_size = sizeof(property_keys) / sizeof(property_keys[0]);
|
||||
|
||||
// Bind CoreCLR
|
||||
if (!coreclr::bind(args.clr_path))
|
||||
if (!coreclr::bind(clr_path))
|
||||
{
|
||||
trace::error(_X("failed to bind to coreclr"));
|
||||
return 1;
|
||||
trace::error(_X("Failed to bind to coreclr"));
|
||||
return StatusCode::CoreClrBindFailure;
|
||||
}
|
||||
|
||||
// Verbose logging
|
||||
if (trace::is_enabled())
|
||||
{
|
||||
for (size_t i = 0; i < property_size; ++i)
|
||||
{
|
||||
pal::string_t key, val;
|
||||
pal::to_palstring(property_keys[i], &key);
|
||||
pal::to_palstring(property_values[i], &val);
|
||||
trace::verbose(_X("Property %s = %s"), key.c_str(), val.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string own_path;
|
||||
pal::to_stdstring(args.own_path.c_str(), &own_path);
|
||||
|
||||
// Initialize CoreCLR
|
||||
coreclr::host_handle_t host_handle;
|
||||
coreclr::domain_id_t domain_id;
|
||||
auto hr = coreclr::initialize(
|
||||
pal::to_stdstring(args.own_path).c_str(),
|
||||
own_path.c_str(),
|
||||
"clrhost",
|
||||
property_keys,
|
||||
property_values,
|
||||
sizeof(property_keys) / sizeof(property_keys[0]),
|
||||
property_size,
|
||||
&host_handle,
|
||||
&domain_id);
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
trace::error(_X("failed to initialize CoreCLR, HRESULT: 0x%X"), hr);
|
||||
return 1;
|
||||
trace::error(_X("Failed to initialize CoreCLR, HRESULT: 0x%X"), hr);
|
||||
return StatusCode::CoreClrInitFailure;
|
||||
}
|
||||
|
||||
// Convert the args (probably not the most performant way to do this...)
|
||||
auto argv_strs = new std::string[args.app_argc];
|
||||
auto argv = new const char*[args.app_argc];
|
||||
if (trace::is_enabled())
|
||||
{
|
||||
pal::string_t arg_str;
|
||||
for (int i = 0; i < args.app_argc; i++)
|
||||
{
|
||||
arg_str.append(args.app_argv[i]);
|
||||
arg_str.append(_X(","));
|
||||
}
|
||||
trace::info(_X("Launch host: %s app: %s, argc: %d args: %s"), args.own_path.c_str(),
|
||||
args.managed_application.c_str(), args.app_argc, arg_str.c_str());
|
||||
}
|
||||
|
||||
// Initialize with empty strings
|
||||
std::vector<std::string> argv_strs(args.app_argc);
|
||||
std::vector<const char*> argv(args.app_argc);
|
||||
for (int i = 0; i < args.app_argc; i++)
|
||||
{
|
||||
argv_strs[i] = pal::to_stdstring(pal::string_t(args.app_argv[i]));
|
||||
pal::to_stdstring(args.app_argv[i], &argv_strs[i]);
|
||||
argv[i] = argv_strs[i].c_str();
|
||||
}
|
||||
|
||||
|
@ -127,21 +197,21 @@ int run(arguments_t args, pal::string_t app_base, tpafile tpa)
|
|||
hr = coreclr::execute_assembly(
|
||||
host_handle,
|
||||
domain_id,
|
||||
args.app_argc,
|
||||
argv,
|
||||
argv.size(),
|
||||
argv.data(),
|
||||
pal::to_stdstring(args.managed_application).c_str(),
|
||||
&exit_code);
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
trace::error(_X("failed to execute managed app, HRESULT: 0x%X"), hr);
|
||||
return 1;
|
||||
trace::error(_X("Failed to execute managed app, HRESULT: 0x%X"), hr);
|
||||
return StatusCode::CoreClrExeFailure;
|
||||
}
|
||||
|
||||
// Shut down the CoreCLR
|
||||
hr = coreclr::shutdown(host_handle, domain_id);
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
trace::warning(_X("failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
|
||||
trace::warning(_X("Failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
|
||||
}
|
||||
|
||||
coreclr::unload();
|
||||
|
@ -155,70 +225,19 @@ int __cdecl wmain(const int argc, const pal::char_t* argv[])
|
|||
int main(const int argc, const pal::char_t* argv[])
|
||||
#endif
|
||||
{
|
||||
// Take care of arguments
|
||||
arguments_t args;
|
||||
if (!parse_arguments(argc, argv, args))
|
||||
{
|
||||
return 1;
|
||||
return StatusCode::InvalidArgFailure;
|
||||
}
|
||||
|
||||
// Resolve paths
|
||||
if (!pal::realpath(args.managed_application))
|
||||
// Resolve CLR path
|
||||
pal::string_t clr_path;
|
||||
if (!resolve_clr_path(args, &clr_path))
|
||||
{
|
||||
trace::error(_X("failed to locate managed application: %s"), args.managed_application.c_str());
|
||||
return 1;
|
||||
trace::error(_X("Could not resolve coreclr path"));
|
||||
return StatusCode::CoreClrResolveFailure;
|
||||
}
|
||||
trace::info(_X("preparing to launch managed application: %s"), args.managed_application.c_str());
|
||||
trace::info(_X("host path: %s"), args.own_path.c_str());
|
||||
|
||||
pal::string_t argstr;
|
||||
for (int i = 0; i < args.app_argc; i++)
|
||||
{
|
||||
argstr.append(args.app_argv[i]);
|
||||
argstr.append(_X(","));
|
||||
}
|
||||
trace::info(_X("App argc: %d"), args.app_argc);
|
||||
trace::info(_X("App argv: %s"), argstr.c_str());
|
||||
|
||||
auto app_base = get_directory(args.managed_application);
|
||||
auto app_name = get_filename(args.managed_application);
|
||||
|
||||
// App-local coreclr wins
|
||||
{
|
||||
pal::string_t candidate;
|
||||
candidate.assign(app_base);
|
||||
append_path(candidate, LIBCORECLR_NAME);
|
||||
|
||||
if (pal::file_exists(candidate))
|
||||
{
|
||||
args.clr_path.assign(app_base);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.clr_path.empty())
|
||||
{
|
||||
trace::error(_X("failed to locate CLR files, set the DOTNET_HOME environment variable"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!pal::realpath(args.clr_path))
|
||||
{
|
||||
trace::error(_X("failed to locate CLR files at %s"), args.clr_path.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
trace::info(_X("using CLR files from: %s"), args.clr_path.c_str());
|
||||
trace::info(_X("preparing to launch: %s"), app_name.c_str());
|
||||
trace::info(_X("using app base: %s"), app_base.c_str());
|
||||
|
||||
// Check for and load deps file
|
||||
pal::string_t tpafile_path;
|
||||
get_tpafile_path(app_base, app_name, tpafile_path);
|
||||
trace::info(_X("checking for .deps File at: %s"), tpafile_path.c_str());
|
||||
tpafile tpa;
|
||||
if (!tpa.load(tpafile_path))
|
||||
{
|
||||
trace::error(_X("invalid .deps file"));
|
||||
return 1;
|
||||
}
|
||||
return run(args, app_base, tpa);
|
||||
return run(args, clr_path);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "utils.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <dlfcn.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
|
@ -19,7 +20,7 @@
|
|||
#define symlinkEntrypointExecutable "/proc/curproc/exe"
|
||||
#endif
|
||||
|
||||
bool pal::find_coreclr(pal::string_t& recv)
|
||||
bool pal::find_coreclr(pal::string_t* recv)
|
||||
{
|
||||
pal::string_t candidate;
|
||||
pal::string_t test;
|
||||
|
@ -28,24 +29,24 @@ bool pal::find_coreclr(pal::string_t& recv)
|
|||
// TODO: These paths should be consistent
|
||||
candidate.assign("/usr/share/dotnet/runtime/coreclr");
|
||||
if (coreclr_exists_in_dir(candidate)) {
|
||||
recv.assign(candidate);
|
||||
recv->assign(candidate);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
candidate.assign("/usr/local/share/dotnet/runtime/coreclr");
|
||||
if (coreclr_exists_in_dir(candidate)) {
|
||||
recv.assign(candidate);
|
||||
recv->assign(candidate);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pal::load_library(const char_t* path, dll_t& dll)
|
||||
bool pal::load_library(const char_t* path, dll_t* dll)
|
||||
{
|
||||
dll = dlopen(path, RTLD_LAZY);
|
||||
if (dll == nullptr)
|
||||
*dll = dlopen(path, RTLD_LAZY);
|
||||
if (*dll == nullptr)
|
||||
{
|
||||
trace::error(_X("failed to load %s, error: %s"), path, dlerror());
|
||||
trace::error(_X("Failed to load %s, error: %s"), path, dlerror());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -56,7 +57,7 @@ pal::proc_t pal::get_symbol(dll_t library, const char* name)
|
|||
auto result = dlsym(library, name);
|
||||
if (result == nullptr)
|
||||
{
|
||||
trace::error(_X("failed to resolve library symbol %s, error: %s"), name, dlerror());
|
||||
trace::error(_X("Failed to resolve library symbol %s, error: %s"), name, dlerror());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -65,7 +66,7 @@ void pal::unload_library(dll_t library)
|
|||
{
|
||||
if (dlclose(library) != 0)
|
||||
{
|
||||
trace::warning(_X("failed to unload library, error: %s"), dlerror());
|
||||
trace::warning(_X("Failed to unload library, error: %s"), dlerror());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,19 +80,20 @@ bool pal::is_path_rooted(const pal::string_t& path)
|
|||
return path.front() == '/';
|
||||
}
|
||||
|
||||
bool pal::get_default_packages_directory(pal::string_t& recv)
|
||||
bool pal::get_default_packages_directory(pal::string_t* recv)
|
||||
{
|
||||
recv->clear();
|
||||
if (!pal::getenv("HOME", recv))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
append_path(recv, _X(".dnx"));
|
||||
append_path(recv, _X("packages"));
|
||||
append_path(&*recv, _X(".dnx"));
|
||||
append_path(&*recv, _X("packages"));
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
bool pal::get_own_executable_path(pal::string_t& recv)
|
||||
bool pal::get_own_executable_path(pal::string_t* recv)
|
||||
{
|
||||
uint32_t path_length = 0;
|
||||
if (_NSGetExecutablePath(nullptr, &path_length) == -1)
|
||||
|
@ -99,28 +101,28 @@ bool pal::get_own_executable_path(pal::string_t& recv)
|
|||
char path_buf[path_length];
|
||||
if (_NSGetExecutablePath(path_buf, &path_length) == 0)
|
||||
{
|
||||
recv.assign(path_buf);
|
||||
recv->assign(path_buf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
bool pal::get_own_executable_path(pal::string_t& recv)
|
||||
bool pal::get_own_executable_path(pal::string_t* recv)
|
||||
{
|
||||
// Just return the symlink to the exe from /proc
|
||||
// We'll call realpath on it later
|
||||
recv.assign(symlinkEntrypointExecutable);
|
||||
recv->assign(symlinkEntrypointExecutable);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool pal::getenv(const pal::char_t* name, pal::string_t& recv)
|
||||
bool pal::getenv(const pal::char_t* name, pal::string_t* recv)
|
||||
{
|
||||
auto result = ::getenv(name);
|
||||
if (result != nullptr)
|
||||
{
|
||||
recv.assign(result);
|
||||
recv->assign(result);
|
||||
}
|
||||
|
||||
// We don't return false. Windows does have a concept of an error reading the variable,
|
||||
|
@ -128,10 +130,10 @@ bool pal::getenv(const pal::char_t* name, pal::string_t& recv)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool pal::realpath(pal::string_t& path)
|
||||
bool pal::realpath(pal::string_t* path)
|
||||
{
|
||||
pal::char_t buf[PATH_MAX];
|
||||
auto resolved = ::realpath(path.c_str(), buf);
|
||||
auto resolved = ::realpath(path->c_str(), buf);
|
||||
if (resolved == nullptr)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
|
@ -141,19 +143,25 @@ bool pal::realpath(pal::string_t& path)
|
|||
perror("realpath()");
|
||||
return false;
|
||||
}
|
||||
path.assign(resolved);
|
||||
path->assign(resolved);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pal::file_exists(const pal::string_t& path)
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
struct stat buffer;
|
||||
return (::stat(path.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
std::vector<pal::string_t> pal::readdir(const pal::string_t& path)
|
||||
void pal::readdir(const pal::string_t& path, std::vector<pal::string_t>* list)
|
||||
{
|
||||
std::vector<pal::string_t> files;
|
||||
assert(list != nullptr);
|
||||
|
||||
std::vector<pal::string_t>& files = *list;
|
||||
|
||||
auto dir = opendir(path.c_str());
|
||||
if (dir != nullptr)
|
||||
|
@ -197,6 +205,4 @@ std::vector<pal::string_t> pal::readdir(const pal::string_t& path)
|
|||
files.push_back(pal::string_t(entry->d_name));
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
|
|
@ -5,23 +5,24 @@
|
|||
#include "trace.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
|
||||
static std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> g_converter;
|
||||
|
||||
bool pal::find_coreclr(pal::string_t& recv)
|
||||
bool pal::find_coreclr(pal::string_t* recv)
|
||||
{
|
||||
pal::string_t candidate;
|
||||
pal::string_t test;
|
||||
|
||||
// Try %LocalAppData%\dotnet
|
||||
if (pal::getenv(_X("LocalAppData"), candidate)) {
|
||||
append_path(candidate, _X("dotnet"));
|
||||
append_path(candidate, _X("runtime"));
|
||||
append_path(candidate, _X("coreclr"));
|
||||
if (pal::getenv(_X("LocalAppData"), &candidate)) {
|
||||
append_path(&candidate, _X("dotnet"));
|
||||
append_path(&candidate, _X("runtime"));
|
||||
append_path(&candidate, _X("coreclr"));
|
||||
if (coreclr_exists_in_dir(candidate)) {
|
||||
recv.assign(candidate);
|
||||
recv->assign(candidate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -30,12 +31,12 @@ bool pal::find_coreclr(pal::string_t& recv)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool pal::load_library(const char_t* path, dll_t& dll)
|
||||
bool pal::load_library(const char_t* path, dll_t* dll)
|
||||
{
|
||||
dll = ::LoadLibraryW(path);
|
||||
if (dll == nullptr)
|
||||
*dll = ::LoadLibraryW(path);
|
||||
if (*dll == nullptr)
|
||||
{
|
||||
trace::error(_X("failed to load coreclr.dll from %s, HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError()));
|
||||
trace::error(_X("Failed to load coreclr.dll from %s, HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -43,15 +44,15 @@ bool pal::load_library(const char_t* path, dll_t& dll)
|
|||
HMODULE dummy_module;
|
||||
if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path, &dummy_module))
|
||||
{
|
||||
trace::error(_X("failed to pin library: %s"));
|
||||
trace::error(_X("Failed to pin library: %s"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trace::is_enabled())
|
||||
{
|
||||
pal::char_t buf[PATH_MAX];
|
||||
::GetModuleFileNameW(dll, buf, PATH_MAX);
|
||||
trace::info(_X("loaded library from %s"), buf);
|
||||
::GetModuleFileNameW(*dll, buf, PATH_MAX);
|
||||
trace::info(_X("Loaded library from %s"), buf);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -67,14 +68,15 @@ void pal::unload_library(dll_t library)
|
|||
// No-op. On windows, we pin the library, so it can't be unloaded.
|
||||
}
|
||||
|
||||
bool pal::get_default_packages_directory(string_t& recv)
|
||||
bool pal::get_default_packages_directory(string_t* recv)
|
||||
{
|
||||
recv->clear();
|
||||
if (!pal::getenv(_X("USERPROFILE"), recv))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
append_path(recv, _X(".dnx"));
|
||||
append_path(recv, _X("packages"));
|
||||
append_path(&*recv, _X(".dnx"));
|
||||
append_path(&*recv, _X("packages"));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -83,7 +85,7 @@ bool pal::is_path_rooted(const string_t& path)
|
|||
return path.length() >= 2 && path[1] == L':';
|
||||
}
|
||||
|
||||
bool pal::getenv(const char_t* name, string_t& recv)
|
||||
bool pal::getenv(const char_t* name, string_t* recv)
|
||||
{
|
||||
auto length = ::GetEnvironmentVariableW(name, nullptr, 0);
|
||||
if (length == 0)
|
||||
|
@ -94,17 +96,17 @@ bool pal::getenv(const char_t* name, string_t& recv)
|
|||
// Leave the receiver empty and return success
|
||||
return true;
|
||||
}
|
||||
trace::error(_X("failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
|
||||
trace::error(_X("Failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
auto buf = new char_t[length];
|
||||
if (::GetEnvironmentVariableW(name, buf, length) == 0)
|
||||
{
|
||||
trace::error(_X("failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
|
||||
trace::error(_X("Failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
recv.assign(buf);
|
||||
recv->assign(buf);
|
||||
delete[] buf;
|
||||
|
||||
return true;
|
||||
|
@ -115,14 +117,14 @@ int pal::xtoi(const char_t* input)
|
|||
return ::_wtoi(input);
|
||||
}
|
||||
|
||||
bool pal::get_own_executable_path(string_t& recv)
|
||||
bool pal::get_own_executable_path(string_t* recv)
|
||||
{
|
||||
char_t program_path[MAX_PATH];
|
||||
DWORD dwModuleFileName = ::GetModuleFileNameW(NULL, program_path, MAX_PATH);
|
||||
if (dwModuleFileName == 0 || dwModuleFileName >= MAX_PATH) {
|
||||
return false;
|
||||
}
|
||||
recv.assign(program_path);
|
||||
recv->assign(program_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -136,21 +138,36 @@ pal::string_t pal::to_palstring(const std::string& str)
|
|||
return g_converter.from_bytes(str);
|
||||
}
|
||||
|
||||
bool pal::realpath(string_t& path)
|
||||
void pal::to_palstring(const char* str, pal::string_t* out)
|
||||
{
|
||||
out->assign(g_converter.from_bytes(str));
|
||||
}
|
||||
|
||||
void pal::to_stdstring(const pal::char_t* str, std::string* out)
|
||||
{
|
||||
out->assign(g_converter.to_bytes(str));
|
||||
}
|
||||
|
||||
bool pal::realpath(string_t* path)
|
||||
{
|
||||
char_t buf[MAX_PATH];
|
||||
auto res = ::GetFullPathNameW(path.c_str(), MAX_PATH, buf, nullptr);
|
||||
auto res = ::GetFullPathNameW(path->c_str(), MAX_PATH, buf, nullptr);
|
||||
if (res == 0 || res > MAX_PATH)
|
||||
{
|
||||
trace::error(_X("error resolving path: %s"), path.c_str());
|
||||
trace::error(_X("Error resolving path: %s"), path->c_str());
|
||||
return false;
|
||||
}
|
||||
path.assign(buf);
|
||||
path->assign(buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pal::file_exists(const string_t& path)
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAW data;
|
||||
auto find_handle = ::FindFirstFileW(path.c_str(), &data);
|
||||
bool found = find_handle != INVALID_HANDLE_VALUE;
|
||||
|
@ -158,9 +175,11 @@ bool pal::file_exists(const string_t& path)
|
|||
return found;
|
||||
}
|
||||
|
||||
std::vector<pal::string_t> pal::readdir(const string_t& path)
|
||||
void pal::readdir(const string_t& path, std::vector<pal::string_t>* list)
|
||||
{
|
||||
std::vector<string_t> files;
|
||||
assert(list != nullptr);
|
||||
|
||||
std::vector<string_t>& files = *list;
|
||||
|
||||
string_t search_string(path);
|
||||
search_string.push_back(DIR_SEPARATOR);
|
||||
|
@ -174,6 +193,4 @@ std::vector<pal::string_t> pal::readdir(const string_t& path)
|
|||
files.push_back(filepath);
|
||||
} while (::FindNextFileW(handle, &data));
|
||||
::FindClose(handle);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
|
128
src/corehost/src/servicing_index.cpp
Normal file
128
src/corehost/src/servicing_index.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
// 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.
|
||||
|
||||
#include "trace.h"
|
||||
#include "servicing_index.h"
|
||||
|
||||
static const pal::char_t* DOTNET_SERVICING_INDEX_TXT = _X("dotnet_servicing_index.txt");
|
||||
|
||||
servicing_index_t::servicing_index_t(const pal::string_t& svc_dir)
|
||||
{
|
||||
m_patch_root = svc_dir;
|
||||
if (!m_patch_root.empty())
|
||||
{
|
||||
m_index_file.assign(m_patch_root);
|
||||
append_path(&m_index_file, DOTNET_SERVICING_INDEX_TXT);
|
||||
}
|
||||
m_parsed = m_index_file.empty() || !pal::file_exists(m_index_file);
|
||||
}
|
||||
|
||||
bool servicing_index_t::find_redirection(
|
||||
const pal::string_t& package_name,
|
||||
const pal::string_t& package_version,
|
||||
const pal::string_t& package_relative,
|
||||
pal::string_t* redirection)
|
||||
{
|
||||
ensure_redirections();
|
||||
|
||||
redirection->clear();
|
||||
|
||||
if (m_redirections.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pal::stringstream_t stream;
|
||||
stream << package_name << _X("|") << package_version << _X("|") << package_relative;
|
||||
|
||||
auto iter = m_redirections.find(stream.str());
|
||||
if (iter != m_redirections.end())
|
||||
{
|
||||
pal::string_t full_path = m_patch_root;
|
||||
append_path(&full_path, iter->second.c_str());
|
||||
if (pal::file_exists(full_path))
|
||||
{
|
||||
*redirection = full_path;
|
||||
trace::verbose(_X("Servicing %s with %s"), stream.str().c_str(), redirection->c_str());
|
||||
return true;
|
||||
}
|
||||
trace::verbose(_X("Serviced file %s doesn't exist"), full_path.c_str());
|
||||
}
|
||||
|
||||
trace::verbose(_X("Entry %s not serviced or file doesn't exist"), stream.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
void servicing_index_t::ensure_redirections()
|
||||
{
|
||||
if (m_parsed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pal::ifstream_t fstream(m_index_file);
|
||||
if (!fstream.good())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pal::stringstream_t sstream;
|
||||
std::string line;
|
||||
while (std::getline(fstream, line))
|
||||
{
|
||||
pal::string_t str;
|
||||
pal::to_palstring(line.c_str(), &str);
|
||||
|
||||
// Can interpret line as "package"?
|
||||
pal::string_t prefix = _X("package|");
|
||||
if (str.find(prefix) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pal::string_t name, version, relative;
|
||||
pal::string_t* tokens[] = { &name, &version, &relative };
|
||||
pal::string_t delim[] = { pal::string_t(_X("|")), pal::string_t(_X("|")), pal::string_t(_X("=")) };
|
||||
|
||||
bool bad_line = false;
|
||||
|
||||
size_t from = prefix.length();
|
||||
for (size_t cur = 0; cur < (sizeof(delim) / sizeof(delim[0])); ++cur)
|
||||
{
|
||||
size_t pos = str.find(delim[cur], from);
|
||||
if (pos == pal::string_t::npos)
|
||||
{
|
||||
bad_line = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tokens[cur]->assign(str.substr(from, pos - from));
|
||||
from = pos + 1;
|
||||
}
|
||||
|
||||
if (bad_line)
|
||||
{
|
||||
trace::error(_X("Invalid line in servicing index. Skipping..."));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save redirection for this package.
|
||||
sstream.str(_X(""));
|
||||
sstream << name << _X("|") << version << _X("|") << relative;
|
||||
|
||||
if (trace::is_enabled())
|
||||
{
|
||||
trace::verbose(_X("Adding servicing entry %s => %s"), sstream.str().c_str(), str.substr(from).c_str());
|
||||
}
|
||||
|
||||
// Store just the filename.
|
||||
pal::string_t redir = str.substr(from);
|
||||
if (_X('/') != DIR_SEPARATOR)
|
||||
{
|
||||
replace_char(&redir, _X('/'), DIR_SEPARATOR);
|
||||
}
|
||||
m_redirections.emplace(sstream.str(), redir);
|
||||
}
|
||||
|
||||
m_parsed = true;
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
// 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.
|
||||
|
||||
#include <set>
|
||||
#include <functional>
|
||||
|
||||
#include "trace.h"
|
||||
#include "tpafile.h"
|
||||
#include "utils.h"
|
||||
|
||||
bool read_field(pal::string_t line, int& offset, pal::string_t& value_recv)
|
||||
{
|
||||
// The first character should be a '"'
|
||||
if (line[offset] != '"')
|
||||
{
|
||||
trace::error(_X("error reading TPA file"));
|
||||
return false;
|
||||
}
|
||||
offset++;
|
||||
|
||||
// Set up destination buffer (it can't be bigger than the original line)
|
||||
pal::char_t buf[PATH_MAX];
|
||||
auto buf_offset = 0;
|
||||
|
||||
// Iterate through characters in the string
|
||||
for (; offset < line.length(); offset++)
|
||||
{
|
||||
// Is this a '\'?
|
||||
if (line[offset] == '\\')
|
||||
{
|
||||
// Skip this character and read the next character into the buffer
|
||||
offset++;
|
||||
buf[buf_offset] = line[offset];
|
||||
}
|
||||
// Is this a '"'?
|
||||
else if (line[offset] == '\"')
|
||||
{
|
||||
// Done! Advance to the pointer after the input
|
||||
offset++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Take the character
|
||||
buf[buf_offset] = line[offset];
|
||||
}
|
||||
buf_offset++;
|
||||
}
|
||||
buf[buf_offset] = '\0';
|
||||
value_recv.assign(buf);
|
||||
|
||||
// Consume the ',' if we have one
|
||||
if (line[offset] == ',')
|
||||
{
|
||||
offset++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tpafile::load(pal::string_t path)
|
||||
{
|
||||
// Check if the file exists, if not, there is nothing to add
|
||||
if (!pal::file_exists(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Open the file
|
||||
pal::ifstream_t file(path);
|
||||
if (!file.good())
|
||||
{
|
||||
// Failed to open the file!
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read lines from the file
|
||||
while (true)
|
||||
{
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
auto line_palstr = pal::to_palstring(line);
|
||||
if (file.eof())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
auto offset = 0;
|
||||
|
||||
tpaentry_t entry;
|
||||
|
||||
// Read fields
|
||||
if (!(read_field(line_palstr, offset, entry.library_type))) return false;
|
||||
if (!(read_field(line_palstr, offset, entry.library_name))) return false;
|
||||
if (!(read_field(line_palstr, offset, entry.library_version))) return false;
|
||||
if (!(read_field(line_palstr, offset, entry.library_hash))) return false;
|
||||
if (!(read_field(line_palstr, offset, entry.asset_type))) return false;
|
||||
if (!(read_field(line_palstr, offset, entry.asset_name))) return false;
|
||||
if (!(read_field(line_palstr, offset, entry.relative_path))) return false;
|
||||
|
||||
m_entries.push_back(entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void tpafile::add_from_local_dir(const pal::string_t& dir)
|
||||
{
|
||||
trace::verbose(_X("adding files from %s to TPA"), dir.c_str());
|
||||
const pal::char_t * const tpa_extensions[] = {
|
||||
_X(".ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
|
||||
_X(".dll"),
|
||||
_X(".ni.exe"),
|
||||
_X(".exe"),
|
||||
};
|
||||
|
||||
std::set<pal::string_t> added_assemblies;
|
||||
|
||||
// Get directory entries
|
||||
auto files = pal::readdir(dir);
|
||||
for (auto ext : tpa_extensions)
|
||||
{
|
||||
auto len = pal::strlen(ext);
|
||||
for (auto file : files)
|
||||
{
|
||||
// Can't be a match if it's the same length as the extension :)
|
||||
if (file.length() > len)
|
||||
{
|
||||
// Extract the same amount of text from the end of file name
|
||||
auto file_ext = file.substr(file.length() - len, len);
|
||||
|
||||
// Check if this file name matches
|
||||
if (pal::strcasecmp(ext, file_ext.c_str()) == 0)
|
||||
{
|
||||
// Get the assembly name by stripping the extension
|
||||
// and add it to the set so we can de-dupe
|
||||
auto asm_name = file.substr(0, file.length() - len);
|
||||
|
||||
// TODO(anurse): Also check if already in TPA file
|
||||
if (added_assemblies.find(asm_name) == added_assemblies.end())
|
||||
{
|
||||
added_assemblies.insert(asm_name);
|
||||
|
||||
tpaentry_t entry;
|
||||
entry.asset_type = pal::string_t(_X("runtime"));
|
||||
entry.library_name = pal::string_t(asm_name);
|
||||
entry.library_version = pal::string_t(_X(""));
|
||||
|
||||
pal::string_t relpath(dir);
|
||||
relpath.push_back(DIR_SEPARATOR);
|
||||
relpath.append(file);
|
||||
entry.relative_path = relpath;
|
||||
entry.asset_name = asm_name;
|
||||
|
||||
trace::verbose(_X("adding %s to TPA list from %s"), asm_name.c_str(), relpath.c_str());
|
||||
m_entries.push_back(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tpafile::write_tpa_list(pal::string_t& output)
|
||||
{
|
||||
std::set<pal::string_t> items;
|
||||
for (auto entry : m_entries)
|
||||
{
|
||||
if (pal::strcmp(entry.asset_type.c_str(), _X("runtime")) == 0 && items.find(entry.asset_name) == items.end())
|
||||
{
|
||||
// Resolve the full path
|
||||
for (auto search_path : m_package_search_paths)
|
||||
{
|
||||
pal::string_t candidate;
|
||||
candidate.reserve(search_path.length() +
|
||||
entry.library_name.length() +
|
||||
entry.library_version.length() +
|
||||
entry.relative_path.length() + 3);
|
||||
candidate.append(search_path);
|
||||
|
||||
append_path(candidate, entry.library_name.c_str());
|
||||
append_path(candidate, entry.library_version.c_str());
|
||||
append_path(candidate, entry.relative_path.c_str());
|
||||
if (pal::file_exists(candidate))
|
||||
{
|
||||
trace::verbose(_X("adding tpa entry: %s"), candidate.c_str());
|
||||
|
||||
output.append(candidate);
|
||||
output.push_back(PATH_SEPARATOR);
|
||||
items.insert(entry.asset_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tpafile::write_native_paths(pal::string_t& output)
|
||||
{
|
||||
std::set<pal::string_t> items;
|
||||
for (auto search_path : m_native_search_paths)
|
||||
{
|
||||
if (items.find(search_path) == items.end())
|
||||
{
|
||||
trace::verbose(_X("adding native search path: %s"), search_path.c_str());
|
||||
output.append(search_path);
|
||||
output.push_back(PATH_SEPARATOR);
|
||||
items.insert(search_path);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto entry : m_entries)
|
||||
{
|
||||
auto dir = entry.relative_path.substr(0, entry.relative_path.find_last_of(DIR_SEPARATOR));
|
||||
if (pal::strcmp(entry.asset_type.c_str(), _X("native")) == 0 && items.find(dir) == items.end())
|
||||
{
|
||||
// Resolve the full path
|
||||
for (auto search_path : m_package_search_paths)
|
||||
{
|
||||
pal::string_t candidate;
|
||||
candidate.reserve(search_path.length() +
|
||||
entry.library_name.length() +
|
||||
entry.library_version.length() +
|
||||
dir.length() + 3);
|
||||
candidate.append(search_path);
|
||||
|
||||
append_path(candidate, entry.library_name.c_str());
|
||||
append_path(candidate, entry.library_version.c_str());
|
||||
append_path(candidate, get_directory(entry.relative_path).c_str());
|
||||
|
||||
if (pal::file_exists(candidate))
|
||||
{
|
||||
trace::verbose(_X("adding native search path: %s"), candidate.c_str());
|
||||
output.append(candidate);
|
||||
output.push_back(PATH_SEPARATOR);
|
||||
items.insert(dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tpafile::add_package_dir(pal::string_t dir)
|
||||
{
|
||||
m_package_search_paths.push_back(dir);
|
||||
}
|
||||
|
||||
void tpafile::add_native_search_path(pal::string_t dir)
|
||||
{
|
||||
m_native_search_paths.push_back(dir);
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
bool coreclr_exists_in_dir(const pal::string_t& candidate)
|
||||
{
|
||||
pal::string_t test(candidate);
|
||||
append_path(test, LIBCORECLR_NAME);
|
||||
append_path(&test, LIBCORECLR_NAME);
|
||||
trace::verbose(_X("checking for CoreCLR in default location: %s"), test.c_str());
|
||||
return pal::file_exists(test);
|
||||
}
|
||||
|
@ -18,19 +18,19 @@ bool ends_with(const pal::string_t& value, const pal::string_t& suffix)
|
|||
(0 == value.compare(value.length() - suffix.length(), suffix.length(), suffix));
|
||||
}
|
||||
|
||||
void append_path(pal::string_t& path1, const pal::char_t* path2)
|
||||
void append_path(pal::string_t* path1, const pal::char_t* path2)
|
||||
{
|
||||
if (pal::is_path_rooted(path2))
|
||||
{
|
||||
path1.assign(path2);
|
||||
path1->assign(path2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (path1.back() != DIR_SEPARATOR)
|
||||
if (path1->empty() || path1->back() != DIR_SEPARATOR)
|
||||
{
|
||||
path1.push_back(DIR_SEPARATOR);
|
||||
path1->push_back(DIR_SEPARATOR);
|
||||
}
|
||||
path1.append(path2);
|
||||
path1->append(path2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,3 +70,12 @@ pal::string_t get_directory(const pal::string_t& path)
|
|||
|
||||
return path.substr(0, path_sep);
|
||||
}
|
||||
|
||||
void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl)
|
||||
{
|
||||
int pos = 0;
|
||||
while ((pos = path->find(match, pos)) != pal::string_t::npos)
|
||||
{
|
||||
(*path)[pos] = repl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,12 +69,6 @@ namespace ConsoleApplication
|
|||
[Fact]
|
||||
public void TestDotnetCompileNativeCpp()
|
||||
{
|
||||
// Skip this test on windows
|
||||
if(SkipForOS(OSPlatform.Windows, "https://github.com/dotnet/cli/issues/335"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TestSetup();
|
||||
|
||||
TestRunCommand("dotnet", $"compile --native --cpp -o {OutputDirectory}");
|
||||
|
@ -90,7 +84,8 @@ namespace ConsoleApplication
|
|||
|
||||
TestRunCommand("dotnet", $"run");
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TestDotnetPack()
|
||||
{
|
||||
TestSetup();
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
// 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 Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Publish.Tests
|
||||
{
|
||||
public class PublishTests : TestBase
|
||||
{
|
||||
private string _testProjectsRoot = @"TestProjects";
|
||||
|
||||
public static IEnumerable<object[]> PublishOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new object[] { "", "", "", "" },
|
||||
new object[] { "dnxcore50", "", "", "" },
|
||||
new object[] { "", RuntimeIdentifier.Current, "", "" },
|
||||
new object[] { "", "", "Release", "" },
|
||||
new object[] { "", "", "", "some/dir"},
|
||||
//new object[] { "", "", "", "\"some/dir/with spaces\"" }, // issue - https://github.com/dotnet/cli/issues/525
|
||||
new object[] { "dnxcore50", RuntimeIdentifier.Current, "Debug", "some/dir" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("PublishOptions")]
|
||||
public void PublishOptionsTest(string framework, string runtime, string config, string outputDir)
|
||||
{
|
||||
// create unique directories in the 'temp' folder
|
||||
var root = Temp.CreateDirectory();
|
||||
var testAppDir = root.CreateDirectory("TestApp");
|
||||
var testLibDir = root.CreateDirectory("TestLibrary");
|
||||
|
||||
//copy projects to the temp dir
|
||||
CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestApp"), testAppDir);
|
||||
CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestLibrary"), testLibDir);
|
||||
|
||||
RunRestore(testAppDir.Path);
|
||||
RunRestore(testLibDir.Path);
|
||||
|
||||
// run publish
|
||||
outputDir = string.IsNullOrEmpty(outputDir) ? "" : Path.Combine(root.Path, outputDir);
|
||||
var testProject = GetProjectPath(testAppDir);
|
||||
var publishCommand = new PublishCommand(testProject, output: outputDir);
|
||||
publishCommand.Execute().Should().Pass();
|
||||
|
||||
// verify the output executable generated
|
||||
var publishedDir = publishCommand.GetOutputDirectory();
|
||||
var outputExe = publishCommand.GetOutputExecutable();
|
||||
var outputPdb = Path.ChangeExtension(outputExe, "pdb");
|
||||
|
||||
// lets make sure that the output exe is runnable
|
||||
var outputExePath = Path.Combine(publishedDir.FullName, publishCommand.GetOutputExecutable());
|
||||
var command = new TestCommand(outputExePath);
|
||||
command.Execute("").Should().ExitWith(100);
|
||||
|
||||
// the pdb should also be published
|
||||
publishedDir.Should().HaveFile(outputPdb);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[ActiveIssue(491)]
|
||||
public void ProjectWithContentsTest()
|
||||
{
|
||||
// create unique directories in the 'temp' folder
|
||||
var testDir = Temp.CreateDirectory();
|
||||
var testAppDir = Path.Combine(_testProjectsRoot, "TestAppWithContents");
|
||||
|
||||
// copy projects to the temp dir
|
||||
CopyProjectToTempDir(testAppDir, testDir);
|
||||
|
||||
RunRestore(testDir.Path);
|
||||
|
||||
// run publish
|
||||
var testProject = GetProjectPath(testDir);
|
||||
var publishCommand = new PublishCommand(testProject);
|
||||
publishCommand.Execute().Should().Pass();
|
||||
|
||||
// make sure that the output dir has the content files
|
||||
publishCommand.GetOutputDirectory().Should().HaveFile("testcontentfile.txt");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BeforeRestoreTest()
|
||||
{
|
||||
// create unique directories in the 'temp' folder
|
||||
var root = Temp.CreateDirectory();
|
||||
var testAppDir = root.CreateDirectory("TestApp");
|
||||
var testLibDir = root.CreateDirectory("TestLibrary");
|
||||
|
||||
// copy projects to the temp dir
|
||||
CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestApp"), testAppDir);
|
||||
CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestLibrary"), testLibDir);
|
||||
|
||||
var testProject = GetProjectPath(testAppDir);
|
||||
var publishCommand = new PublishCommand(testProject);
|
||||
publishCommand.Execute().Should().Fail();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LibraryPublishTest()
|
||||
{
|
||||
// create unique directories in the 'temp' folder
|
||||
var root = Temp.CreateDirectory();
|
||||
var testLibDir = root.CreateDirectory("TestLibrary");
|
||||
|
||||
//copy projects to the temp dir
|
||||
CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestLibrary"), testLibDir);
|
||||
|
||||
RunRestore(testLibDir.Path);
|
||||
|
||||
var testProject = GetProjectPath(testLibDir);
|
||||
var publishCommand = new PublishCommand(testProject);
|
||||
publishCommand.Execute().Should().Pass();
|
||||
|
||||
publishCommand.GetOutputDirectory().Should().NotHaveFile("TestLibrary.exe");
|
||||
publishCommand.GetOutputDirectory().Should().HaveFile("TestLibrary.dll");
|
||||
publishCommand.GetOutputDirectory().Should().HaveFile("TestLibrary.pdb");
|
||||
// dependencies should also be copied
|
||||
publishCommand.GetOutputDirectory().Should().HaveFile("System.Runtime.dll");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompilationFailedTest()
|
||||
{
|
||||
var testDir = Temp.CreateDirectory();
|
||||
var compileFailDir = Path.Combine(_testProjectsRoot, "CompileFail");
|
||||
|
||||
CopyProjectToTempDir(compileFailDir, testDir);
|
||||
|
||||
RunRestore(testDir.Path);
|
||||
|
||||
var testProject = GetProjectPath(testDir);
|
||||
var publishCommand = new PublishCommand(testProject);
|
||||
|
||||
publishCommand.Execute().Should().Fail();
|
||||
}
|
||||
|
||||
private void CopyProjectToTempDir(string projectDir, TempDirectory tempDir)
|
||||
{
|
||||
// copy all the files to temp dir
|
||||
foreach (var file in Directory.EnumerateFiles(projectDir))
|
||||
{
|
||||
// never copy project.lock.json. All the tests are expected to call 'dotnet restore'
|
||||
if (file.ToLower().EndsWith("project.lock.json"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tempDir.CopyFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetProjectPath(TempDirectory projectDir)
|
||||
{
|
||||
return Path.Combine(projectDir.Path, "project.json");
|
||||
}
|
||||
|
||||
private void RunRestore(string args)
|
||||
{
|
||||
var restoreCommand = new RestoreCommand();
|
||||
restoreCommand.Execute($"--quiet {args}").Should().Pass();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>386d412c-003c-47b1-8258-0e35865cb7c4</ProjectGuid>
|
||||
<RootNamespace>Microsoft.DotNet.Tools.Publish.Tests</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
20
test/Microsoft.DotNet.Tools.Publish.Tests/project.json
Normal file
20
test/Microsoft.DotNet.Tools.Publish.Tests/project.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.0.0-rc2-23614",
|
||||
"Microsoft.NETCore.TestHost" : "1.0.0-*",
|
||||
|
||||
"xunit": "2.1.0",
|
||||
"xunit.console.netcore": "1.0.2-prerelease-00101",
|
||||
"xunit.netcore.extensions": "1.0.0-prerelease-*",
|
||||
"xunit.runner.utility": "2.1.0",
|
||||
|
||||
"Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }
|
||||
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// 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 FluentAssertions;
|
||||
using FluentAssertions.Execution;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public class CommandResultAssertions
|
||||
{
|
||||
private CommandResult _commandResult;
|
||||
|
||||
public CommandResultAssertions(CommandResult commandResult)
|
||||
{
|
||||
_commandResult = commandResult;
|
||||
}
|
||||
|
||||
public AndConstraint<CommandResultAssertions> ExitWith(int expectedExitCode)
|
||||
{
|
||||
Execute.Assertion.ForCondition(_commandResult.ExitCode == expectedExitCode)
|
||||
.FailWith("Expected command to exit with {0} but it exited with {1}.", expectedExitCode, _commandResult.ExitCode);
|
||||
return new AndConstraint<CommandResultAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<CommandResultAssertions> Pass()
|
||||
{
|
||||
Execute.Assertion.ForCondition(_commandResult.ExitCode == 0)
|
||||
.FailWith("Expected command to pass but it exited with {0}.", _commandResult.ExitCode);
|
||||
return new AndConstraint<CommandResultAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<CommandResultAssertions> Fail()
|
||||
{
|
||||
Execute.Assertion.ForCondition(_commandResult.ExitCode != 0)
|
||||
.FailWith("Expected command to fail but it exited with {0}.", _commandResult.ExitCode);
|
||||
return new AndConstraint<CommandResultAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<CommandResultAssertions> HaveStdOut()
|
||||
{
|
||||
Execute.Assertion.ForCondition(!string.IsNullOrEmpty(_commandResult.StdOut))
|
||||
.FailWith("Command did not output anything to stdout");
|
||||
return new AndConstraint<CommandResultAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<CommandResultAssertions> HaveStdErr()
|
||||
{
|
||||
Execute.Assertion.ForCondition(!string.IsNullOrEmpty(_commandResult.StdErr))
|
||||
.FailWith("Command did not output anything to stderr");
|
||||
return new AndConstraint<CommandResultAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<CommandResultAssertions> NotHaveStdOut()
|
||||
{
|
||||
Execute.Assertion.ForCondition(string.IsNullOrEmpty(_commandResult.StdOut))
|
||||
.FailWith("Expected command to not output to stdout but found - {0}{1}", Environment.NewLine, _commandResult.StdOut);
|
||||
return new AndConstraint<CommandResultAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<CommandResultAssertions> NotHaveStdErr()
|
||||
{
|
||||
Execute.Assertion.ForCondition(string.IsNullOrEmpty(_commandResult.StdErr))
|
||||
.FailWith("Expected command to not output to stderr but found - {0}{1}", Environment.NewLine, _commandResult.StdErr);
|
||||
return new AndConstraint<CommandResultAssertions>(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// 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.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public static class CommandResultExtensions
|
||||
{
|
||||
public static CommandResultAssertions Should(this CommandResult commandResult)
|
||||
{
|
||||
return new CommandResultAssertions(commandResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// 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 FluentAssertions;
|
||||
using FluentAssertions.Execution;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public class DirectoryInfoAssertions
|
||||
{
|
||||
private DirectoryInfo _dirInfo;
|
||||
|
||||
public DirectoryInfoAssertions(DirectoryInfo dir)
|
||||
{
|
||||
_dirInfo = dir;
|
||||
}
|
||||
|
||||
public AndConstraint<DirectoryInfoAssertions> Exist()
|
||||
{
|
||||
_dirInfo.Exists.Should().BeTrue();
|
||||
Execute.Assertion.ForCondition(_dirInfo.Exists)
|
||||
.FailWith("Expected directory {0} does not exist.", _dirInfo.FullName);
|
||||
return new AndConstraint<DirectoryInfoAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<DirectoryInfoAssertions> HaveFile(string expectedFile)
|
||||
{
|
||||
var file = _dirInfo.EnumerateFiles(expectedFile, SearchOption.TopDirectoryOnly).SingleOrDefault();
|
||||
Execute.Assertion.ForCondition(file != null)
|
||||
.FailWith("Expected File {0} cannot be found in directory {1}.", expectedFile, _dirInfo.FullName);
|
||||
return new AndConstraint<DirectoryInfoAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<DirectoryInfoAssertions> NotHaveFile(string expectedFile)
|
||||
{
|
||||
var file = _dirInfo.EnumerateFiles(expectedFile, SearchOption.TopDirectoryOnly).SingleOrDefault();
|
||||
Execute.Assertion.ForCondition(file == null)
|
||||
.FailWith("File {0} should not be found in directory {1}.", expectedFile, _dirInfo.FullName);
|
||||
return new AndConstraint<DirectoryInfoAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<DirectoryInfoAssertions> HaveFiles(IEnumerable<string> expectedFiles)
|
||||
{
|
||||
foreach (var expectedFile in expectedFiles)
|
||||
{
|
||||
HaveFile(expectedFile);
|
||||
}
|
||||
|
||||
return new AndConstraint<DirectoryInfoAssertions>(this);
|
||||
}
|
||||
|
||||
public AndConstraint<DirectoryInfoAssertions> HaveDirectory(string expectedDir)
|
||||
{
|
||||
var dir = _dirInfo.EnumerateDirectories(expectedDir, SearchOption.TopDirectoryOnly).SingleOrDefault();
|
||||
Execute.Assertion.ForCondition(dir != null)
|
||||
.FailWith("Expected directory {0} cannot be found inside directory {1}.", expectedDir, _dirInfo.FullName);
|
||||
|
||||
return new AndConstraint<DirectoryInfoAssertions>(new DirectoryInfoAssertions(dir));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public static class DirectoryInfoExtensions
|
||||
{
|
||||
public static DirectoryInfoAssertions Should(this DirectoryInfo dir)
|
||||
{
|
||||
return new DirectoryInfoAssertions(dir);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Publish.Tests
|
||||
{
|
||||
public sealed class PublishCommand : TestCommand
|
||||
{
|
||||
private Project _project;
|
||||
private string _path;
|
||||
private string _framework;
|
||||
private string _runtime;
|
||||
private string _config;
|
||||
private string _output;
|
||||
|
||||
public PublishCommand(string projectPath, string framework="", string runtime="", string output="", string config="")
|
||||
: base("dotnet")
|
||||
{
|
||||
_path = projectPath;
|
||||
_project = ProjectReader.GetProject(projectPath);
|
||||
_framework = framework;
|
||||
_runtime = runtime;
|
||||
_output = output;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override CommandResult Execute(string args="")
|
||||
{
|
||||
args = $"publish {BuildArgs()} {args}";
|
||||
return base.Execute(args);
|
||||
}
|
||||
|
||||
public string ProjectName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _project.Name;
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildRelativeOutputPath()
|
||||
{
|
||||
// lets try to build an approximate output path
|
||||
string config = string.IsNullOrEmpty(_config) ? "Debug" : _config;
|
||||
string framework = string.IsNullOrEmpty(_framework) ?
|
||||
_project.GetTargetFrameworks().First().FrameworkName.GetShortFolderName() : _framework;
|
||||
string runtime = string.IsNullOrEmpty(_runtime) ? RuntimeIdentifier.Current : _runtime;
|
||||
string output = Path.Combine("bin", config, framework, runtime);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public DirectoryInfo GetOutputDirectory()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_output))
|
||||
{
|
||||
return new DirectoryInfo(_output);
|
||||
}
|
||||
|
||||
string output = Path.Combine(_project.ProjectDirectory, BuildRelativeOutputPath());
|
||||
return new DirectoryInfo(output);
|
||||
}
|
||||
|
||||
public string GetOutputExecutable()
|
||||
{
|
||||
var result = _project.Name;
|
||||
result += RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : "";
|
||||
return result;
|
||||
}
|
||||
|
||||
private string BuildArgs()
|
||||
{
|
||||
return $"{_path} {GetFrameworkOption()} {GetRuntimeOption()} {GetOutputOption()} {GetConfigOption()}";
|
||||
}
|
||||
|
||||
private string GetFrameworkOption()
|
||||
{
|
||||
return string.IsNullOrEmpty(_framework) ? "" : $"-f {_framework}";
|
||||
}
|
||||
|
||||
private string GetRuntimeOption()
|
||||
{
|
||||
return string.IsNullOrEmpty(_runtime) ? "" : $"-r {_runtime}";
|
||||
}
|
||||
|
||||
private string GetOutputOption()
|
||||
{
|
||||
return string.IsNullOrEmpty(_output) ? "" : $"-o {_output}";
|
||||
}
|
||||
|
||||
private string GetConfigOption()
|
||||
{
|
||||
return string.IsNullOrEmpty(_config) ? "" : $"-c {_output}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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.Utils;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Publish.Tests
|
||||
{
|
||||
public sealed class RestoreCommand : TestCommand
|
||||
{
|
||||
public RestoreCommand()
|
||||
: base("dotnet")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override CommandResult Execute(string args="")
|
||||
{
|
||||
args = $"restore {args}";
|
||||
return base.Execute(args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// 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.Utils;
|
||||
using System;
|
||||
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public class TestCommand
|
||||
{
|
||||
protected string _command;
|
||||
|
||||
public TestCommand(string command)
|
||||
{
|
||||
_command = command;
|
||||
}
|
||||
|
||||
public virtual CommandResult Execute(string args)
|
||||
{
|
||||
Console.WriteLine($"Executing - {_command} {args}");
|
||||
var commandResult = Command.Create(_command, args)
|
||||
.ForwardStdErr()
|
||||
.ForwardStdOut()
|
||||
.Execute();
|
||||
|
||||
return commandResult;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>e4f46eab-b5a5-4e60-9b9d-40a1fadbf45c</ProjectGuid>
|
||||
<RootNamespace>Microsoft.DotNet.Tools.Test.Utilities</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
|
@ -0,0 +1,30 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public sealed class DisposableDirectory : TempDirectory, IDisposable
|
||||
{
|
||||
public DisposableDirectory(TempRoot root)
|
||||
: base(root)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Path != null && Directory.Exists(Path))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(Path, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// 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 Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public sealed class DisposableFile : TempFile, IDisposable
|
||||
{
|
||||
public DisposableFile(string path)
|
||||
: base(path)
|
||||
{
|
||||
}
|
||||
|
||||
public DisposableFile(string prefix = null, string extension = null, string directory = null, string callerSourcePath = null, int callerLineNumber = 0)
|
||||
: base(prefix, extension, directory, callerSourcePath, callerLineNumber)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Path != null && File.Exists(Path))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Path);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
try
|
||||
{
|
||||
// the file might still be memory-mapped, delete on close:
|
||||
DeleteFileOnClose(Path);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(@"
|
||||
The file '{0}' seems to have been opened in a way that prevents us from deleting it on close.
|
||||
Is the file loaded as an assembly (e.g. via Assembly.LoadFile)?
|
||||
|
||||
{1}: {2}", Path, ex.GetType().Name, ex.Message), ex);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// We should ignore this exception if we got it the second time,
|
||||
// the most important reason is that the file has already been
|
||||
// scheduled for deletion and will be deleted when all handles
|
||||
// are closed.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", PreserveSig = false)]
|
||||
private static extern void SetFileInformationByHandle(SafeFileHandle handle, int fileInformationClass, ref uint fileDispositionInfoDeleteFile, int bufferSize);
|
||||
|
||||
private const int FileDispositionInfo = 4;
|
||||
|
||||
internal static void PrepareDeleteOnCloseStreamForDisposal(FileStream stream)
|
||||
{
|
||||
// tomat: Set disposition to "delete" on the stream, so to avoid ForeFront EndPoint
|
||||
// Protection driver scanning the file. Note that after calling this on a file that's open with DeleteOnClose,
|
||||
// the file can't be opened again, not even by the same process.
|
||||
uint trueValue = 1;
|
||||
SetFileInformationByHandle(stream.SafeFileHandle, FileDispositionInfo, ref trueValue, sizeof(uint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks given file for automatic deletion when all its handles are closed.
|
||||
/// Note that after doing this the file can't be opened again, not even by the same process.
|
||||
/// </summary>
|
||||
internal static void DeleteFileOnClose(string fullPath)
|
||||
{
|
||||
using (var stream = new FileStream(fullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.Delete | FileShare.ReadWrite, 8, FileOptions.DeleteOnClose))
|
||||
{
|
||||
PrepareDeleteOnCloseStreamForDisposal(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a few file name utilities that are needed by the compiler.
|
||||
/// In general the compiler is not supposed to understand the format of the paths.
|
||||
/// In rare cases it needs to check if a string is a valid file name or change the extension
|
||||
/// (embedded resources, netmodules, output name).
|
||||
/// The APIs are intentionally limited to cover just these rare cases. Do not add more APIs.
|
||||
/// </summary>
|
||||
internal static class FileNameUtilities
|
||||
{
|
||||
private const string DirectorySeparatorStr = "\\";
|
||||
internal const char DirectorySeparatorChar = '\\';
|
||||
internal const char AltDirectorySeparatorChar = '/';
|
||||
internal const char VolumeSeparatorChar = ':';
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the string represents an unqualified file name.
|
||||
/// The name may contain any characters but directory and volume separators.
|
||||
/// </summary>
|
||||
/// <param name="path">Path.</param>
|
||||
/// <returns>
|
||||
/// True if <paramref name="path"/> is a simple file name, false if it is null or includes a directory specification.
|
||||
/// </returns>
|
||||
internal static bool IsFileName(string path)
|
||||
{
|
||||
return IndexOfFileName(path) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the offset in <paramref name="path"/> where the dot that starts an extension is, or -1 if the path doesn't have an extension.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns 0 for path ".foo".
|
||||
/// Returns -1 for path "foo.".
|
||||
/// </remarks>
|
||||
private static int IndexOfExtension(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int length = path.Length;
|
||||
int i = length;
|
||||
|
||||
while (--i >= 0)
|
||||
{
|
||||
char c = path[i];
|
||||
if (c == '.')
|
||||
{
|
||||
if (i != length - 1)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar || c == VolumeSeparatorChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an extension of the specified path string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same functionality as <see cref="System.IO.Path.GetExtension(string)"/> but doesn't throw an exception
|
||||
/// if there are invalid characters in the path.
|
||||
/// </remarks>
|
||||
internal static string GetExtension(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = IndexOfExtension(path);
|
||||
return (index >= 0) ? path.Substring(index) : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes extension from path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns "foo" for path "foo.".
|
||||
/// Returns "foo.." for path "foo...".
|
||||
/// </remarks>
|
||||
private static string RemoveExtension(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = IndexOfExtension(path);
|
||||
if (index >= 0)
|
||||
{
|
||||
return path.Substring(0, index);
|
||||
}
|
||||
|
||||
// trim last ".", if present
|
||||
if (path.Length > 0 && path[path.Length - 1] == '.')
|
||||
{
|
||||
return path.Substring(0, path.Length - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns path with the extension changed to <paramref name="extension"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Equivalent of <see cref="System.IO.Path.ChangeExtension(string, string)"/>
|
||||
///
|
||||
/// If <paramref name="path"/> is null, returns null.
|
||||
/// If path does not end with an extension, the new extension is appended to the path.
|
||||
/// If extension is null, equivalent to <see cref="RemoveExtension"/>.
|
||||
/// </returns>
|
||||
internal static string ChangeExtension(string path, string extension)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var pathWithoutExtension = RemoveExtension(path);
|
||||
if (extension == null || path.Length == 0)
|
||||
{
|
||||
return pathWithoutExtension;
|
||||
}
|
||||
|
||||
if (extension.Length == 0 || extension[0] != '.')
|
||||
{
|
||||
return pathWithoutExtension + "." + extension;
|
||||
}
|
||||
|
||||
return pathWithoutExtension + extension;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the position in given path where the file name starts.
|
||||
/// </summary>
|
||||
/// <returns>-1 if path is null.</returns>
|
||||
internal static int IndexOfFileName(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = path.Length - 1; i >= 0; i--)
|
||||
{
|
||||
char ch = path[i];
|
||||
if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file name from path.
|
||||
/// </summary>
|
||||
/// <remarks>Unlike <see cref="System.IO.Path.GetFileName"/> doesn't check for invalid path characters.</remarks>
|
||||
internal static string GetFileName(string path)
|
||||
{
|
||||
int fileNameStart = IndexOfFileName(path);
|
||||
return (fileNameStart <= 0) ? path : path.Substring(fileNameStart);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// 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.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// The collection of extension methods for the <see cref="ImmutableArray{T}"/> type
|
||||
/// </summary>
|
||||
public static class ImmutableArrayTestExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes read-only array of bytes to the specified file.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Data to write to the file.</param>
|
||||
/// <param name="path">File path.</param>
|
||||
internal static void WriteToFile(this ImmutableArray<byte> bytes, string path)
|
||||
{
|
||||
Debug.Assert(!bytes.IsDefault);
|
||||
|
||||
const int bufferSize = 4096;
|
||||
using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize))
|
||||
{
|
||||
// PERF: Consider using an ObjectPool<byte[]> here
|
||||
byte[] buffer = new byte[Math.Min(bufferSize, bytes.Length)];
|
||||
|
||||
int offset = 0;
|
||||
while (offset < bytes.Length)
|
||||
{
|
||||
int length = Math.Min(bufferSize, bytes.Length - offset);
|
||||
bytes.CopyTo(offset, buffer, 0, length);
|
||||
fileStream.Write(buffer, 0, length);
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
internal enum PathKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Null or empty.
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// "file"
|
||||
/// </summary>
|
||||
Relative,
|
||||
|
||||
/// <summary>
|
||||
/// ".\file"
|
||||
/// </summary>
|
||||
RelativeToCurrentDirectory,
|
||||
|
||||
/// <summary>
|
||||
/// "..\file"
|
||||
/// </summary>
|
||||
RelativeToCurrentParent,
|
||||
|
||||
/// <summary>
|
||||
/// "\dir\file"
|
||||
/// </summary>
|
||||
RelativeToCurrentRoot,
|
||||
|
||||
/// <summary>
|
||||
/// "C:dir\file"
|
||||
/// </summary>
|
||||
RelativeToDriveDirectory,
|
||||
|
||||
/// <summary>
|
||||
/// "C:\file" or "\\machine" (UNC).
|
||||
/// </summary>
|
||||
Absolute,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
// Contains path parsing utilities.
|
||||
// We need our own because System.IO.Path is insufficient for our purposes
|
||||
// For example we need to be able to work with invalid paths or paths containing wildcards
|
||||
internal static class PathUtilities
|
||||
{
|
||||
// We consider '/' a directory separator on Unix like systems.
|
||||
// On Windows both / and \ are equally accepted.
|
||||
internal static readonly char DirectorySeparatorChar = IsUnixLikePlatform ? '/' : '\\';
|
||||
internal static readonly char AltDirectorySeparatorChar = '/';
|
||||
internal static readonly string DirectorySeparatorStr = new string(DirectorySeparatorChar, 1);
|
||||
internal const char VolumeSeparatorChar = ':';
|
||||
|
||||
private static bool IsUnixLikePlatform
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.DirectorySeparatorChar == '/';
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsDirectorySeparator(char c)
|
||||
{
|
||||
return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
|
||||
}
|
||||
|
||||
internal static string TrimTrailingSeparators(string s)
|
||||
{
|
||||
int lastSeparator = s.Length;
|
||||
while (lastSeparator > 0 && IsDirectorySeparator(s[lastSeparator - 1]))
|
||||
{
|
||||
lastSeparator = lastSeparator - 1;
|
||||
}
|
||||
|
||||
if (lastSeparator != s.Length)
|
||||
{
|
||||
s = s.Substring(0, lastSeparator);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
internal static string GetExtension(string path)
|
||||
{
|
||||
return FileNameUtilities.GetExtension(path);
|
||||
}
|
||||
|
||||
internal static string ChangeExtension(string path, string extension)
|
||||
{
|
||||
return FileNameUtilities.ChangeExtension(path, extension);
|
||||
}
|
||||
|
||||
internal static string RemoveExtension(string path)
|
||||
{
|
||||
return FileNameUtilities.ChangeExtension(path, extension: null);
|
||||
}
|
||||
|
||||
internal static string GetFileName(string path)
|
||||
{
|
||||
return FileNameUtilities.GetFileName(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get directory name from path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unlike <see cref="System.IO.Path.GetDirectoryName"/> it
|
||||
/// doesn't check for invalid path characters,
|
||||
/// doesn't strip any trailing directory separators (TODO: tomat),
|
||||
/// doesn't recognize UNC structure \\computer-name\share\directory-name\file-name (TODO: tomat).
|
||||
/// </remarks>
|
||||
/// <returns>Prefix of path that represents a directory. </returns>
|
||||
internal static string GetDirectoryName(string path)
|
||||
{
|
||||
int fileNameStart = FileNameUtilities.IndexOfFileName(path);
|
||||
if (fileNameStart < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return path.Substring(0, fileNameStart);
|
||||
}
|
||||
|
||||
internal static PathKind GetPathKind(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return PathKind.Empty;
|
||||
}
|
||||
|
||||
// "C:\"
|
||||
// "\\machine" (UNC)
|
||||
// "/etc" (Unix)
|
||||
if (IsAbsolute(path))
|
||||
{
|
||||
return PathKind.Absolute;
|
||||
}
|
||||
|
||||
// "."
|
||||
// ".."
|
||||
// ".\"
|
||||
// "..\"
|
||||
if (path.Length > 0 && path[0] == '.')
|
||||
{
|
||||
if (path.Length == 1 || IsDirectorySeparator(path[1]))
|
||||
{
|
||||
return PathKind.RelativeToCurrentDirectory;
|
||||
}
|
||||
|
||||
if (path[1] == '.')
|
||||
{
|
||||
if (path.Length == 2 || IsDirectorySeparator(path[2]))
|
||||
{
|
||||
return PathKind.RelativeToCurrentParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsUnixLikePlatform)
|
||||
{
|
||||
// "\"
|
||||
// "\foo"
|
||||
if (path.Length >= 1 && IsDirectorySeparator(path[0]))
|
||||
{
|
||||
return PathKind.RelativeToCurrentRoot;
|
||||
}
|
||||
|
||||
// "C:foo"
|
||||
|
||||
if (path.Length >= 2 && path[1] == VolumeSeparatorChar && (path.Length <= 2 || !IsDirectorySeparator(path[2])))
|
||||
{
|
||||
return PathKind.RelativeToDriveDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
// "foo.dll"
|
||||
return PathKind.Relative;
|
||||
}
|
||||
|
||||
internal static bool IsAbsolute(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsUnixLikePlatform)
|
||||
{
|
||||
return path[0] == DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
// "C:\"
|
||||
if (IsDriveRootedAbsolutePath(path))
|
||||
{
|
||||
// Including invalid paths (e.g. "*:\")
|
||||
return true;
|
||||
}
|
||||
|
||||
// "\\machine\share"
|
||||
// Including invalid/incomplete UNC paths (e.g. "\\foo")
|
||||
return path.Length >= 2 &&
|
||||
IsDirectorySeparator(path[0]) &&
|
||||
IsDirectorySeparator(path[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if given path is absolute and starts with a drive specification ("C:\").
|
||||
/// </summary>
|
||||
private static bool IsDriveRootedAbsolutePath(string path)
|
||||
{
|
||||
Debug.Assert(!IsUnixLikePlatform);
|
||||
return path.Length >= 3 && path[1] == VolumeSeparatorChar && IsDirectorySeparator(path[2]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a prefix of given path which is the root of the path.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Root of an absolute path or null if the path isn't absolute or has invalid format (e.g. "\\").
|
||||
/// It may or may not end with a directory separator (e.g. "C:\", "C:\foo", "\\machine\share", etc.) .
|
||||
/// </returns>
|
||||
internal static string GetPathRoot(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int length = GetPathRootLength(path);
|
||||
return (length != -1) ? path.Substring(0, length) : null;
|
||||
}
|
||||
|
||||
private static int GetPathRootLength(string path)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(path));
|
||||
|
||||
if (IsUnixLikePlatform)
|
||||
{
|
||||
if (IsDirectorySeparator(path[0]))
|
||||
{
|
||||
// "/*"
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// "C:\"
|
||||
if (IsDriveRootedAbsolutePath(path))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (IsDirectorySeparator(path[0]))
|
||||
{
|
||||
// "\\machine\share"
|
||||
return GetUncPathRootLength(path);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the length of root of an UNC path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// "\\server\share" is root of UNC path "\\server\share\dir1\dir2\file".
|
||||
/// </remarks>
|
||||
private static int GetUncPathRootLength(string path)
|
||||
{
|
||||
Debug.Assert(IsDirectorySeparator(path[0]));
|
||||
|
||||
// root:
|
||||
// [directory-separator]{2,}[^directory-separator]+[directory-separator]+[^directory-separator]+
|
||||
|
||||
int serverIndex = IndexOfNonDirectorySeparator(path, 1);
|
||||
if (serverIndex < 2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int separator = IndexOfDirectorySeparator(path, serverIndex);
|
||||
if (separator == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int shareIndex = IndexOfNonDirectorySeparator(path, separator);
|
||||
if (shareIndex == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rootEnd = IndexOfDirectorySeparator(path, shareIndex);
|
||||
return rootEnd == -1 ? path.Length : rootEnd;
|
||||
}
|
||||
|
||||
private static int IndexOfDirectorySeparator(string path, int start)
|
||||
{
|
||||
for (int i = start; i < path.Length; i++)
|
||||
{
|
||||
if (IsDirectorySeparator(path[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int IndexOfNonDirectorySeparator(string path, int start)
|
||||
{
|
||||
for (int i = start; i < path.Length; i++)
|
||||
{
|
||||
if (!IsDirectorySeparator(path[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines an absolute path with a relative.
|
||||
/// </summary>
|
||||
/// <param name="root">Absolute root path.</param>
|
||||
/// <param name="relativePath">Relative path.</param>
|
||||
/// <returns>
|
||||
/// An absolute combined path, or null if <paramref name="relativePath"/> is
|
||||
/// absolute (e.g. "C:\abc", "\\machine\share\abc"),
|
||||
/// relative to the current root (e.g. "\abc"),
|
||||
/// or relative to a drive directory (e.g. "C:abc\def").
|
||||
/// </returns>
|
||||
/// <seealso cref="CombinePossiblyRelativeAndRelativePaths"/>
|
||||
internal static string CombineAbsoluteAndRelativePaths(string root, string relativePath)
|
||||
{
|
||||
Debug.Assert(IsAbsolute(root));
|
||||
|
||||
return CombinePossiblyRelativeAndRelativePaths(root, relativePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine two paths, the first of which may be absolute.
|
||||
/// </summary>
|
||||
/// <param name="rootOpt">First path: absolute, relative, or null.</param>
|
||||
/// <param name="relativePath">Second path: relative and non-null.</param>
|
||||
/// <returns>null, if <paramref name="rootOpt"/> is null; a combined path, otherwise.</returns>
|
||||
/// <seealso cref="CombineAbsoluteAndRelativePaths"/>
|
||||
internal static string CombinePossiblyRelativeAndRelativePaths(string rootOpt, string relativePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rootOpt))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (GetPathKind(relativePath))
|
||||
{
|
||||
case PathKind.Empty:
|
||||
return rootOpt;
|
||||
|
||||
case PathKind.Absolute:
|
||||
case PathKind.RelativeToCurrentRoot:
|
||||
case PathKind.RelativeToDriveDirectory:
|
||||
return null;
|
||||
}
|
||||
|
||||
return CombinePathsUnchecked(rootOpt, relativePath);
|
||||
}
|
||||
|
||||
internal static string CombinePathsUnchecked(string root, string relativePath)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(root));
|
||||
|
||||
char c = root[root.Length - 1];
|
||||
if (!IsDirectorySeparator(c) && c != VolumeSeparatorChar)
|
||||
{
|
||||
return root + DirectorySeparatorStr + relativePath;
|
||||
}
|
||||
|
||||
return root + relativePath;
|
||||
}
|
||||
|
||||
internal static string RemoveTrailingDirectorySeparator(string path)
|
||||
{
|
||||
if (path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]))
|
||||
{
|
||||
return path.Substring(0, path.Length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an assembly reference is considered an assembly file path or an assembly name.
|
||||
/// used, for example, on values of /r and #r.
|
||||
/// </summary>
|
||||
internal static bool IsFilePath(string assemblyDisplayNameOrPath)
|
||||
{
|
||||
Debug.Assert(assemblyDisplayNameOrPath != null);
|
||||
|
||||
string extension = FileNameUtilities.GetExtension(assemblyDisplayNameOrPath);
|
||||
return string.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(extension, ".exe", StringComparison.OrdinalIgnoreCase)
|
||||
|| assemblyDisplayNameOrPath.IndexOf(DirectorySeparatorChar) != -1
|
||||
|| assemblyDisplayNameOrPath.IndexOf(AltDirectorySeparatorChar) != -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public class TempDirectory
|
||||
{
|
||||
private readonly string _path;
|
||||
private readonly TempRoot _root;
|
||||
|
||||
protected TempDirectory(TempRoot root)
|
||||
: this(CreateUniqueDirectory(TempRoot.Root), root)
|
||||
{
|
||||
}
|
||||
|
||||
private TempDirectory(string path, TempRoot root)
|
||||
{
|
||||
Debug.Assert(path != null);
|
||||
Debug.Assert(root != null);
|
||||
|
||||
_path = path;
|
||||
_root = root;
|
||||
}
|
||||
|
||||
private static string CreateUniqueDirectory(string basePath)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
string dir = System.IO.Path.Combine(basePath, Guid.NewGuid().ToString());
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
return dir;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get { return _path; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a file in this directory.
|
||||
/// </summary>
|
||||
/// <param name="name">File name.</param>
|
||||
public TempFile CreateFile(string name)
|
||||
{
|
||||
string filePath = System.IO.Path.Combine(_path, name);
|
||||
TempRoot.CreateStream(filePath);
|
||||
return _root.AddFile(new DisposableFile(filePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a file in this directory that is a copy of the specified file.
|
||||
/// </summary>
|
||||
public TempFile CopyFile(string originalPath)
|
||||
{
|
||||
string name = System.IO.Path.GetFileName(originalPath);
|
||||
string filePath = System.IO.Path.Combine(_path, name);
|
||||
File.Copy(originalPath, filePath);
|
||||
return _root.AddFile(new DisposableFile(filePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a subdirectory in this directory.
|
||||
/// </summary>
|
||||
/// <param name="name">Directory name or unrooted directory path.</param>
|
||||
public TempDirectory CreateDirectory(string name)
|
||||
{
|
||||
string dirPath = System.IO.Path.Combine(_path, name);
|
||||
Directory.CreateDirectory(dirPath);
|
||||
return new TempDirectory(dirPath, _root);
|
||||
}
|
||||
|
||||
public void SetCurrentDirectory()
|
||||
{
|
||||
Directory.SetCurrentDirectory(_path);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// 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.Immutable;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public class TempFile
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
internal TempFile(string path)
|
||||
{
|
||||
Debug.Assert(PathUtilities.IsAbsolute(path));
|
||||
_path = path;
|
||||
}
|
||||
|
||||
internal TempFile(string prefix, string extension, string directory, string callerSourcePath, int callerLineNumber)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (prefix == null)
|
||||
{
|
||||
prefix = System.IO.Path.GetFileName(callerSourcePath) + "_" + callerLineNumber.ToString() + "_";
|
||||
}
|
||||
|
||||
_path = System.IO.Path.Combine(directory ?? TempRoot.Root, prefix + Guid.NewGuid() + (extension ?? ".tmp"));
|
||||
|
||||
try
|
||||
{
|
||||
TempRoot.CreateStream(_path);
|
||||
break;
|
||||
}
|
||||
catch (PathTooLongException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FileStream Open(FileAccess access = FileAccess.ReadWrite)
|
||||
{
|
||||
return new FileStream(_path, FileMode.Open, access);
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get { return _path; }
|
||||
}
|
||||
|
||||
public TempFile WriteAllText(string content, Encoding encoding)
|
||||
{
|
||||
File.WriteAllText(_path, content, encoding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TempFile WriteAllText(string content)
|
||||
{
|
||||
File.WriteAllText(_path, content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<TempFile> WriteAllTextAsync(string content, Encoding encoding)
|
||||
{
|
||||
using (var sw = new StreamWriter(File.Create(_path), encoding))
|
||||
{
|
||||
await sw.WriteAsync(content).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task<TempFile> WriteAllTextAsync(string content)
|
||||
{
|
||||
return WriteAllTextAsync(content, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public TempFile WriteAllBytes(byte[] content)
|
||||
{
|
||||
File.WriteAllBytes(_path, content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TempFile WriteAllBytes(ImmutableArray<byte> content)
|
||||
{
|
||||
content.WriteToFile(_path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public string ReadAllText()
|
||||
{
|
||||
return File.ReadAllText(_path);
|
||||
}
|
||||
|
||||
public TempFile CopyContentFrom(string path)
|
||||
{
|
||||
return WriteAllBytes(File.ReadAllBytes(path));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public sealed class TempRoot : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> _temps = new List<IDisposable>();
|
||||
public static readonly string Root;
|
||||
|
||||
static TempRoot()
|
||||
{
|
||||
Root = Path.Combine(Path.GetTempPath(), "DotnetCLITests");
|
||||
Directory.CreateDirectory(Root);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_temps != null)
|
||||
{
|
||||
DisposeAll(_temps);
|
||||
_temps.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisposeAll(IEnumerable<IDisposable> temps)
|
||||
{
|
||||
foreach (var temp in temps)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (temp != null)
|
||||
{
|
||||
temp.Dispose();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TempDirectory CreateDirectory()
|
||||
{
|
||||
var dir = new DisposableDirectory(this);
|
||||
_temps.Add(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
public TempFile CreateFile(string prefix = null, string extension = null, string directory = null, [CallerFilePath]string callerSourcePath = null, [CallerLineNumber]int callerLineNumber = 0)
|
||||
{
|
||||
return AddFile(new DisposableFile(prefix, extension, directory, callerSourcePath, callerLineNumber));
|
||||
}
|
||||
|
||||
public DisposableFile AddFile(DisposableFile file)
|
||||
{
|
||||
_temps.Add(file);
|
||||
return file;
|
||||
}
|
||||
|
||||
internal static void CreateStream(string fullPath)
|
||||
{
|
||||
using (var file = new FileStream(fullPath, FileMode.CreateNew)) { }
|
||||
}
|
||||
}
|
||||
}
|
49
test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs
Normal file
49
test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all unit test classes.
|
||||
/// </summary>
|
||||
public abstract class TestBase : IDisposable
|
||||
{
|
||||
private TempRoot _temp;
|
||||
|
||||
protected TestBase()
|
||||
{
|
||||
}
|
||||
|
||||
public static string GetUniqueName()
|
||||
{
|
||||
return Guid.NewGuid().ToString("D");
|
||||
}
|
||||
|
||||
public TempRoot Temp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_temp == null)
|
||||
{
|
||||
_temp = new TempRoot();
|
||||
}
|
||||
|
||||
return _temp;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (_temp != null)
|
||||
{
|
||||
_temp.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
test/Microsoft.DotNet.Tools.Tests.Utilities/project.json
Normal file
22
test/Microsoft.DotNet.Tools.Tests.Utilities/project.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"description": "Microsoft.DotNet.Tools.Tests.Utilities Class Library",
|
||||
|
||||
"dependencies": {
|
||||
"System.Collections": "4.0.11-*",
|
||||
"System.Collections.Immutable": "1.1.38-*",
|
||||
"System.Linq": "4.0.1-*",
|
||||
"System.Threading": "4.0.11-*",
|
||||
"System.IO.FileSystem": "4.0.1-*",
|
||||
"System.IO": "4.0.11-*",
|
||||
"System.Runtime.InteropServices": "4.0.21-*",
|
||||
"FluentAssertions": "4.0.0",
|
||||
|
||||
"Microsoft.DotNet.Cli.Utils": { "target": "project" }
|
||||
},
|
||||
|
||||
|
||||
"frameworks": {
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
|
@ -8,9 +8,10 @@ namespace TestApp
|
|||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Console.WriteLine(TestLibrary.Helper.GetMessage());
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
}
|
13
test/TestProjects/TestAppWithContents/Program.cs
Normal file
13
test/TestProjects/TestAppWithContents/Program.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace ConsoleApplication
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
}
|
18
test/TestProjects/TestAppWithContents/project.json
Normal file
18
test/TestProjects/TestAppWithContents/project.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"compilationOptions": {
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Runtime": "1.0.1-beta-*",
|
||||
"System.IO": "4.0.10-beta-*",
|
||||
"System.Console": "4.0.0-beta-*",
|
||||
"System.Runtime": "4.0.21-beta-*"
|
||||
},
|
||||
|
||||
|
||||
"frameworks": {
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
This is to test if contents are copied correctly by the dotnet tools
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue