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:
Sridhar Periyasamy 2015-12-23 16:22:59 -08:00
commit 3363707704
102 changed files with 3847 additions and 823 deletions

View file

@ -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. 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. 2. git (available from http://www.git-scm.com/) on the PATH.
3. clang (available from http://clang.llvm.org) on the PATH. 3. clang (available from http://clang.llvm.org) on the PATH.
### For OS X ### For OS X
1. Xcode 1. Xcode
@ -37,7 +38,7 @@ In order to build .NET Command Line Interface, you need the following installed
##Adding a Command ##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. 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. 1. Add a new project for the command.

View file

@ -61,6 +61,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0722D325
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MultiProjectValidator", "tools\MultiProjectValidator\MultiProjectValidator.xproj", "{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MultiProjectValidator", "tools\MultiProjectValidator\MultiProjectValidator.xproj", "{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|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.ActiveCfg = Release|Any CPU
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}.RelWithDebInfo|x64.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -469,5 +487,6 @@ Global
{7A75ACC4-3C2F-44E1-B492-0EC08704E9FF} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {7A75ACC4-3C2F-44E1-B492-0EC08704E9FF} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{BC765FBF-AD7A-4A99-9902-5540C5A74181} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {BC765FBF-AD7A-4A99-9902-5540C5A74181} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85} = {0722D325-24C8-4E83-B5AF-0A083E7F0749} {08A68C6A-86F6-4ED2-89A7-B166D33E9F85} = {0722D325-24C8-4E83-B5AF-0A083E7F0749}
{688870C8-9843-4F9E-8576-D39290AD0F25} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -59,18 +59,18 @@ Compiling to IL is done using:
dotnet compile dotnet compile
This will drop a binary in `./bin/[configuration]/[framework]/[binary name]` that you can just run. 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. Finally, you can also try out native compilation using RyuJIT as shown below:
**Note:** at this point, only the `helloworld` and `dotnetbot` samples will work with native compilation.
dotnet compile --native 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 dotnet compile --native --cpp
This will drop a native single binary in `./bin/[configuration]/[framework]/native/[binary name]` that you can run. 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). For more details, please refer to the [documentation](https://github.com/dotnet/corert/tree/master/Documentation).
Building from source Building from source

View file

@ -4,7 +4,8 @@
# Licensed under the MIT license. See LICENSE file in the project root for full license information. # 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 set -e
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
@ -27,12 +28,19 @@ do
debug) debug)
export CONFIGURATION=Debug export CONFIGURATION=Debug
;; ;;
offline)
export OFFLINE=true
;;
*) *)
esac esac
done done
[ -z "$CONFIGURATION" ] && CONFIGURATION=Debug [ -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 # 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 export DOTNET_INSTALL_DIR=$DIR/.dotnet_stage0/$RID
[ -d $DOTNET_INSTALL_DIR ] || mkdir -p $DOTNET_INSTALL_DIR [ -d $DOTNET_INSTALL_DIR ] || mkdir -p $DOTNET_INSTALL_DIR

View file

@ -6,11 +6,8 @@
$Rid = "win7-x64" $Rid = "win7-x64"
$Tfm = "dnxcore50" $Tfm = "dnxcore50"
$DnxVersion = "1.0.0-rc1-update1"
$RepoRoot = Convert-Path "$PSScriptRoot\.." $RepoRoot = Convert-Path "$PSScriptRoot\.."
$OutputDir = "$RepoRoot\artifacts\$Rid" $OutputDir = "$RepoRoot\artifacts\$Rid"
$DnxDir = "$OutputDir\dnx"
$Stage1Dir = "$OutputDir\stage1" $Stage1Dir = "$OutputDir\stage1"
$Stage2Dir = "$OutputDir\stage2" $Stage2Dir = "$OutputDir\stage2"
$HostDir = "$OutputDir\corehost" $HostDir = "$OutputDir\corehost"

View file

@ -4,7 +4,8 @@
# #
param( param(
[string]$Configuration="Debug") [string]$Configuration="Debug",
[switch]$Offline)
. "$PSScriptRoot\_common.ps1" . "$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 ***" 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 (!$?) { if (!$?) {
Write-Host "Building dotnet tools finished with errors." Write-Host "Building dotnet tools finished with errors."
Exit 1 Exit 1

View file

@ -3,7 +3,8 @@
# Licensed under the MIT license. See LICENSE file in the project root for full license information. # 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" $ErrorActionPreference="Stop"
@ -13,6 +14,36 @@ $ErrorActionPreference="Stop"
$StartPath = $env:PATH $StartPath = $env:PATH
$StartDotNetHome = $env:DOTNET_HOME $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 { try {
# Check prereqs # Check prereqs
@ -23,40 +54,35 @@ Download it from https://www.cmake.org
"@ "@
} }
# Install a stage 0 if($Offline){
header "Installing dotnet stage 0" Write-Host "Skipping Stage 0, Dnx, and Packages dowlnoad: Offline build"
& "$PSScriptRoot\install.ps1"
if (!$?) {
Write-Host "Command failed: $PSScriptRoot\install.ps1"
Exit 1
} }
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 # Put stage 0 on the path
$DotNetTools = $env:DOTNET_INSTALL_DIR $DotNetTools = $env:DOTNET_INSTALL_DIR
if (!$DotNetTools) { if (!$DotNetTools) {
$DotNetTools = "$($env:LOCALAPPDATA)\Microsoft\dotnet" $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" header "Building corehost"
pushd "$RepoRoot\src\corehost" pushd "$RepoRoot\src\corehost"
try { try {
@ -131,18 +157,11 @@ Download it from https://www.cmake.org
Exit 1 Exit 1
} }
# Smoke test stage2
$env:DOTNET_HOME = "$Stage2Dir" $env:DOTNET_HOME = "$Stage2Dir"
& "$PSScriptRoot\test\smoke-test.ps1" # Run tests on stage2 dotnet tools
& "$PSScriptRoot\test\runtests.ps1"
if (!$?) { if (!$?) {
Write-Host "Command failed: $PSScriptRoot\test\smoke-test.ps1" Write-Host "Command failed: $PSScriptRoot\test\runtests.ps1"
Exit 1
}
# E2E Test of stage2
& "$PSScriptRoot\test\e2e-test.ps1"
if (!$?) {
Write-Host "Command failed: $PSScriptRoot\test\e2e-test.ps1"
Exit 1 Exit 1
} }

View file

@ -56,24 +56,27 @@ fi
[ -z "$CONFIGURATION" ] && export CONFIGURATION=Debug [ -z "$CONFIGURATION" ] && export CONFIGURATION=Debug
# Download DNX to copy into stage2 if [[ ! -z "$OFFLINE" ]]; then
getDnx 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 # And put the stage0 on the PATH
$DIR/install.sh export PATH=$REPOROOT/artifacts/$RID/stage0/bin:$PATH
# And put the stage0 on the PATH # Intentionally clear the DOTNET_TOOLS path, we want to use the default installed version
export PATH=$REPOROOT/artifacts/$RID/stage0/bin:$PATH unset DOTNET_TOOLS
# Intentionally clear the DOTNET_TOOLS path, we want to use the default installed version DOTNET_PATH=$(which dotnet)
unset DOTNET_TOOLS PREFIX="$(cd -P "$(dirname "$DOTNET_PATH")/.." && pwd)"
DOTNET_PATH=$(which dotnet) header "Restoring packages"
PREFIX="$(cd -P "$(dirname "$DOTNET_PATH")/.." && pwd)" $DNX_ROOT/dnu restore "$REPOROOT" --quiet --runtime "$RID" --no-cache
fi
header "Restoring packages"
$DNX_ROOT/dnu restore "$REPOROOT" --quiet --runtime "$RID" --no-cache
header "Building corehost" header "Building corehost"
@ -140,16 +143,12 @@ fi
COMMIT_ID=$(git rev-parse HEAD) COMMIT_ID=$(git rev-parse HEAD)
echo $COMMIT_ID > $STAGE2_DIR/.commit echo $COMMIT_ID > $STAGE2_DIR/.commit
# Smoke-test the output # Skipping tests for centos
header "Testing stage2 ..."
DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/smoke-test.sh
# Skipping E2E tests for centos
# tracked by issue - https://github.com/dotnet/corefx/issues/5066 # tracked by issue - https://github.com/dotnet/corefx/issues/5066
if [ "$OSNAME" != "centos" ]; then if [ "$OSNAME" != "centos" ]; then
# E2E test on the output # Run tests on the stage2 output
header "Testing stage2 End to End ..." header "Testing stage2..."
DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/e2e-test.sh DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/runtests.sh
fi fi
# Run Validation for Project.json dependencies # Run Validation for Project.json dependencies

View file

@ -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
}

View file

@ -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
View 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
View 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

View file

@ -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

View file

@ -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

View file

@ -5,7 +5,7 @@ using System.IO;
namespace Microsoft.DotNet.Cli.Utils namespace Microsoft.DotNet.Cli.Utils
{ {
internal struct CommandResult public struct CommandResult
{ {
public static readonly CommandResult Empty = new CommandResult(); public static readonly CommandResult Empty = new CommandResult();

View file

@ -1,6 +1,10 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"compilationOptions": {
"keyFile": "../../tools/Key.snk"
},
"dependencies": { "dependencies": {
"System.Reflection": "4.0.10-rc2-23616", "System.Reflection": "4.0.10-rc2-23616",
"NETStandard.Library": "1.0.0-rc2-23616", "NETStandard.Library": "1.0.0-rc2-23616",

View file

@ -1,5 +1,8 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"compilationOptions": {
"keyFile": "../../tools/Key.snk"
},
"dependencies": { "dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616", "NETStandard.Library": "1.0.0-rc2-23616",
"Microsoft.DotNet.ProjectModel": "1.0.0-*", "Microsoft.DotNet.ProjectModel": "1.0.0-*",

View file

@ -1,5 +1,8 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"compilationOptions": {
"keyFile": "../../tools/Key.snk"
},
"dependencies": { "dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616", "NETStandard.Library": "1.0.0-rc2-23616",
"Microsoft.DotNet.ProjectModel": "1.0.0-*", "Microsoft.DotNet.ProjectModel": "1.0.0-*",

View file

@ -29,6 +29,8 @@ namespace Microsoft.DotNet.ProjectModel
public bool? EmitEntryPoint { get; set; } public bool? EmitEntryPoint { get; set; }
public bool? PreserveCompilationContext { get; set; }
public static CommonCompilerOptions Combine(params CommonCompilerOptions[] options) public static CommonCompilerOptions Combine(params CommonCompilerOptions[] options)
{ {
var result = new CommonCompilerOptions(); var result = new CommonCompilerOptions();
@ -91,6 +93,11 @@ namespace Microsoft.DotNet.ProjectModel
{ {
result.EmitEntryPoint = option.EmitEntryPoint; result.EmitEntryPoint = option.EmitEntryPoint;
} }
if (option.PreserveCompilationContext != null)
{
result.PreserveCompilationContext = option.PreserveCompilationContext;
}
} }
return result; return result;

View file

@ -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);
}
}
}

View file

@ -525,7 +525,8 @@ namespace Microsoft.DotNet.ProjectModel
KeyFile = rawOptions.ValueAsString("keyFile"), KeyFile = rawOptions.ValueAsString("keyFile"),
DelaySign = rawOptions.ValueAsNullableBoolean("delaySign"), DelaySign = rawOptions.ValueAsNullableBoolean("delaySign"),
PublicSign = rawOptions.ValueAsNullableBoolean("publicSign"), PublicSign = rawOptions.ValueAsNullableBoolean("publicSign"),
EmitEntryPoint = rawOptions.ValueAsNullableBoolean("emitEntryPoint") EmitEntryPoint = rawOptions.ValueAsNullableBoolean("emitEntryPoint"),
PreserveCompilationContext = rawOptions.ValueAsNullableBoolean("preserveCompilationContext")
}; };
} }

View file

@ -1,5 +1,8 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"compilationOptions": {
"keyFile": "../../tools/Key.snk"
},
"description": "Types to model a .NET Project", "description": "Types to model a .NET Project",
"dependencies": { "dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616", "NETStandard.Library": "1.0.0-rc2-23616",
@ -19,6 +22,10 @@
"Microsoft.Extensions.HashCodeCombiner.Sources": { "Microsoft.Extensions.HashCodeCombiner.Sources": {
"type": "build", "type": "build",
"version": "1.0.0-*" "version": "1.0.0-*"
},
"Microsoft.Extensions.DependencyModel": {
"type": "build",
"version": "1.0.0-*"
} }
}, },

View file

@ -1,7 +1,7 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"compilationOptions": { "compilationOptions": {
"emitEntryPoint": true "emitEntryPoint": true
}, },
"dependencies": { "dependencies": {

View file

@ -16,6 +16,8 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
public IEnumerable<string> LinkLibPaths { get; set; } public IEnumerable<string> LinkLibPaths { get; set; }
public string AppDepSDKPath { get; set; } public string AppDepSDKPath { get; set; }
public string IlcPath { get; set; } public string IlcPath { get; set; }
public string IlcSdkPath { get; set; }
public string CppCompilerFlags { get; set; }
public bool IsHelp { get; set; } public bool IsHelp { get; set; }
public int ReturnCode { get; set; } public int ReturnCode { get; set; }
@ -55,6 +57,12 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
if (!string.IsNullOrEmpty(IlcPath)) if (!string.IsNullOrEmpty(IlcPath))
{ {
config.IlcPath = IlcPath; config.IlcPath = IlcPath;
config.IlcSdkPath = IlcPath;
}
if (!string.IsNullOrEmpty(IlcSdkPath))
{
config.IlcSdkPath = IlcSdkPath;
} }
if (!string.IsNullOrEmpty(LogPath)) if (!string.IsNullOrEmpty(LogPath))
@ -67,6 +75,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
config.IlcArgs = IlcArgs; config.IlcArgs = IlcArgs;
} }
if (!string.IsNullOrWhiteSpace(CppCompilerFlags))
{
config.CppCompilerFlags = CppCompilerFlags;
}
foreach (var reference in ReferencePaths) foreach (var reference in ReferencePaths)
{ {
config.AddReference(reference); config.AddReference(reference);

View file

@ -17,11 +17,13 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
NativeIntermediateMode? nativeMode = null; NativeIntermediateMode? nativeMode = null;
string ilcArgs = null; string ilcArgs = null;
string ilcPath = null; string ilcPath = null;
string ilcSdkPath = null;
string appDepSdk = null; string appDepSdk = null;
string logPath = null; string logPath = null;
var help = false; var help = false;
string helpText = null; string helpText = null;
var returnCode = 0; var returnCode = 0;
string cppCompilerFlags = null;
IReadOnlyList<string> references = Array.Empty<string>(); IReadOnlyList<string> references = Array.Empty<string>();
IReadOnlyList<string> linklib = 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 // 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("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"); syntax.DefineOptionList("linklib", ref linklib, "Use to link in additional static libs");
// TEMPORARY Hack until CoreRT compatible Framework Libs are available // TEMPORARY Hack until CoreRT compatible Framework Libs are available
@ -54,6 +58,9 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Optional Log Path // Optional Log Path
syntax.DefineOption("logpath", ref logPath, "Use to dump Native Compilation Logs to a file."); 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.DefineOption("h|help", ref help, "Help for compile native.");
syntax.DefineParameter("INPUT_ASSEMBLY", ref inputAssembly, syntax.DefineParameter("INPUT_ASSEMBLY", ref inputAssembly,
@ -125,9 +132,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
ReferencePaths = references, ReferencePaths = references,
IlcArgs = ilcArgs, IlcArgs = ilcArgs,
IlcPath = ilcPath, IlcPath = ilcPath,
IlcSdkPath = ilcSdkPath,
LinkLibPaths = linklib, LinkLibPaths = linklib,
AppDepSDKPath = appDepSdk, AppDepSDKPath = appDepSdk,
LogPath = logPath LogPath = logPath,
CppCompilerFlags = cppCompilerFlags
}; };
} }
} }

View file

@ -46,11 +46,10 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
argsList.Add($"\"{inputFilePath}\""); argsList.Add($"\"{inputFilePath}\"");
// System.Private.CoreLib Reference // System.Private.CoreLib Reference
String[] coreLibs = new String[] { "System.Private.CoreLib.dll", "System.Private.Corelib.dll" }; var coreLibPath = Path.Combine(config.IlcSdkPath, "System.Private.CoreLib.dll");
var coreLibPath = Path.Combine(config.IlcPath, Array.Find(coreLibs, lib => File.Exists(Path.Combine(config.IlcPath, lib))));
argsList.Add($"-r \"{coreLibPath}\""); argsList.Add($"-r \"{coreLibPath}\"");
// Dependency References // AppDep References
foreach (var reference in config.ReferencePaths) foreach (var reference in config.ReferencePaths)
{ {
argsList.Add($"-r \"{reference}\""); argsList.Add($"-r \"{reference}\"");

View file

@ -19,7 +19,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
private readonly string cLibsFlags = "-lm -ldl"; private readonly string cLibsFlags = "-lm -ldl";
private readonly string cflags = "-g -lstdc++ -lrt -Wno-invalid-offsetof -pthread"; 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", "libbootstrappercpp.a",
"libPortableRuntime.a", "libPortableRuntime.a",
@ -65,24 +65,29 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Flags // Flags
argsList.Add(cflags); 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("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/ubuntu.14.04")); argsList.Add($"\"{ilcSdkIncPath}\"");
argsList.Add("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk"));
// Input File // Input File
var inCppFile = DetermineInFile(config); var inCppFile = DetermineInFile(config);
argsList.Add(inCppFile); argsList.Add(inCppFile);
// Add Stubs // Pass the optional native compiler flags if specified
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/ubuntu.14.04/lxstubs.cpp")); if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
// Libs
foreach (var lib in libs)
{ {
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); argsList.Add(libPath);
} }

View file

@ -20,7 +20,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// TODO: debug/release support // TODO: debug/release support
private readonly string cflags = "-lstdc++ -lpthread -ldl -lm -lrt"; private readonly string cflags = "-lstdc++ -lpthread -ldl -lm -lrt";
private readonly string[] libs = new string[] private readonly string[] IlcSdkLibs = new string[]
{ {
"libbootstrapper.a", "libbootstrapper.a",
"libRuntime.a", "libRuntime.a",
@ -70,10 +70,16 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
var inLibFile = DetermineInFile(config); var inLibFile = DetermineInFile(config);
argsList.Add(inLibFile); argsList.Add(inLibFile);
// Libs // Pass the optional native compiler flags if specified
foreach (var lib in libs) 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); argsList.Add(libPath);
} }

View file

@ -21,7 +21,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Link to iconv APIs // Link to iconv APIs
private readonly string libFlags = "-liconv"; private readonly string libFlags = "-liconv";
private readonly string[] libs = new string[] private readonly string[] IlcSdkLibs = new string[]
{ {
"libbootstrappercpp.a", "libbootstrappercpp.a",
"libPortableRuntime.a", "libPortableRuntime.a",
@ -67,27 +67,32 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Flags // Flags
argsList.Add(cflags); 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("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10")); argsList.Add($"\"{ilcSdkIncPath}\"");
argsList.Add("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk"));
// Input File // Input File
var inCppFile = DetermineInFile(config); var inCppFile = DetermineInFile(config);
argsList.Add(inCppFile); argsList.Add(inCppFile);
// Add Stubs
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10/osxstubs.cpp"));
// Lib flags // Lib flags
argsList.Add(libFlags); argsList.Add(libFlags);
// Libs // Pass the optional native compiler flags if specified
foreach (var lib in libs) 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 // Forward the library to linked to the linker
argsList.Add("-Xlinker"); argsList.Add("-Xlinker");

View file

@ -20,7 +20,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// TODO: debug/release support // TODO: debug/release support
private readonly string cflags = "-g -lstdc++ -Wno-invalid-offsetof -pthread -ldl -lm -liconv"; 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", "libbootstrapper.a",
"libRuntime.a", "libRuntime.a",
@ -66,19 +66,20 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Flags // Flags
argsList.Add(cflags); argsList.Add(cflags);
// Add Stubs // Pass the optional native compiler flags if specified
argsList.Add("-I "+Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10")); if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
argsList.Add("-I "+Path.Combine(config.AppDepSDKPath, "CPPSdk")); {
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10/osxstubs.cpp")); argsList.Add(config.CppCompilerFlags);
}
// Input File // Input File
var inLibFile = DetermineInFile(config); var inLibFile = DetermineInFile(config);
argsList.Add("-Xlinker "+inLibFile); argsList.Add("-Xlinker "+inLibFile);
// Libs // ILC SDK Libs
foreach (var lib in libs) foreach (var lib in IlcSdkLibs)
{ {
var libPath = Path.Combine(config.IlcPath, lib); var libPath = Path.Combine(config.IlcSdkPath, lib);
argsList.Add("-Xlinker "+libPath); argsList.Add("-Xlinker "+libPath);
} }

View file

@ -22,7 +22,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
private static readonly Dictionary<BuildConfiguration, string> ConfigurationCompilerOptionsMap = new Dictionary<BuildConfiguration, string> 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" } { 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"); argsList.Add("/c");
// Add Includes // 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("/I");
argsList.Add($"\"{win7CppSdkPath}\""); argsList.Add($"\"{ilcSdkIncPath}\"");
var cppSdkPath = Path.Combine(config.AppDepSDKPath, "CPPSdk");
argsList.Add("/I");
argsList.Add($"\"{cppSdkPath}\"");
// Configuration Based Compiler Options // Configuration Based Compiler Options
argsList.Add(ConfigurationCompilerOptionsMap[config.BuildType]); argsList.Add(ConfigurationCompilerOptionsMap[config.BuildType]);
// Pass the optional native compiler flags if specified
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
{
argsList.Add(config.CppCompilerFlags);
}
// Output // Output
var objOut = DetermineOutputFile(config); var objOut = DetermineOutputFile(config);
argsList.Add($"/Fo\"{objOut}\""); argsList.Add($"/Fo\"{objOut}\"");

View file

@ -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" } { 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.cpp, new string[] { "PortableRuntime.lib", "bootstrappercpp.lib" } },
{ NativeIntermediateMode.ryujit, new string[] { "Runtime.lib", "bootstrapper.lib" } } { NativeIntermediateMode.ryujit, new string[] { "Runtime.lib", "bootstrapper.lib" } }
@ -46,9 +46,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
"odbccp32.lib" "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[]>() 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" } } { BuildConfiguration.release , new string[] { "msvcrt.lib" } }
}; };
@ -98,11 +100,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Constant Libs // Constant Libs
argsList.Add(string.Join(" ", ConstantLinkLibs)); argsList.Add(string.Join(" ", ConstantLinkLibs));
// SDK Libs // ILC SDK Libs
var SDKLibs = ModeLibMap[config.NativeMode]; var SDKLibs = IlcSdkLibMap[config.NativeMode];
foreach (var lib in SDKLibs) foreach (var lib in SDKLibs)
{ {
var sdkLibPath = Path.Combine(config.IlcPath, lib); var sdkLibPath = Path.Combine(config.IlcSdkPath, lib);
argsList.Add($"\"{sdkLibPath}\""); argsList.Add($"\"{sdkLibPath}\"");
} }

View file

@ -14,12 +14,14 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
private string _inputManagedAssemblyPath; private string _inputManagedAssemblyPath;
private string _appDepSdkPath; private string _appDepSdkPath;
private string _ilcPath; private string _ilcPath;
private string _ilcSdkPath;
private string _outputDirectory; private string _outputDirectory;
private string _intermediateDirectory; private string _intermediateDirectory;
private string _logPath; private string _logPath;
private string _ilcArgs; private string _ilcArgs;
private readonly List<string> _referencePaths; private readonly List<string> _referencePaths;
private readonly List<string> _linkLibPaths; private readonly List<string> _linkLibPaths;
private string _cppCompilerFlags;
public string LogPath 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() private NativeCompileSettings()
{ {
_linkLibPaths = new List<string>(); _linkLibPaths = new List<string>();
_referencePaths = new List<string>(); _referencePaths = new List<string>();
IlcPath = AppContext.BaseDirectory; IlcPath = AppContext.BaseDirectory;
// By default, ILC SDK Path is assumed to be the same folder as ILC path
IlcSdkPath = IlcPath;
Architecture = DefaultArchitectureMode; Architecture = DefaultArchitectureMode;
BuildType = DefaultBuiltType; BuildType = DefaultBuiltType;
NativeMode = DefaultNativeModel; NativeMode = DefaultNativeModel;

View file

@ -5,7 +5,7 @@
"emitEntryPoint": true "emitEntryPoint": true
}, },
"dependencies": { "dependencies": {
"Microsoft.DotNet.AppDep":"1.0.2-*" "Microsoft.DotNet.AppDep":"1.0.3-*"
}, },
"frameworks": { "frameworks": {
"dnxcore50": { } "dnxcore50": { }

View file

@ -15,8 +15,8 @@
"type": "build", "type": "build",
"version": "1.0.0-*" "version": "1.0.0-*"
}, },
"Microsoft.DotNet.ILCompiler": "1.0.3-*", "Microsoft.DotNet.ILCompiler": "1.0.4-*",
"Microsoft.DotNet.ILCompiler.SDK": "1.0.3-*", "Microsoft.DotNet.ILCompiler.SDK": "1.0.4-*",
"Microsoft.DotNet.Compiler.Common": "1.0.0-*" "Microsoft.DotNet.Compiler.Common": "1.0.0-*"
}, },
"frameworks": { "frameworks": {

View file

@ -16,6 +16,7 @@ using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Compilation; using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Utilities; using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks; using NuGet.Frameworks;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.DotNet.Tools.Compiler 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 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 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 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 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(() => app.OnExecute(() =>
{ {
@ -63,9 +66,11 @@ namespace Microsoft.DotNet.Tools.Compiler
var ilcArgsValue = ilcArgs.Value(); var ilcArgsValue = ilcArgs.Value();
var ilcPathValue = ilcPath.Value(); var ilcPathValue = ilcPath.Value();
var ilcSdkPathValue = ilcSdkPath.Value(); var ilcSdkPathValue = ilcSdkPath.Value();
var appDepSdkPathValue = appDepSdkPath.Value();
var configValue = configuration.Value() ?? Constants.DefaultConfiguration; var configValue = configuration.Value() ?? Constants.DefaultConfiguration;
var outputValue = output.Value(); var outputValue = output.Value();
var intermediateValue = intermediateOutput.Value(); var intermediateValue = intermediateOutput.Value();
var cppCompilerFlagsValue = cppCompilerFlags.Value();
// Load project contexts for each framework and compile them // Load project contexts for each framework and compile them
bool success = true; bool success = true;
@ -77,7 +82,7 @@ namespace Microsoft.DotNet.Tools.Compiler
success &= Compile(context, configValue, outputValue, intermediateValue, buildProjectReferences, noHost.HasValue()); success &= Compile(context, configValue, outputValue, intermediateValue, buildProjectReferences, noHost.HasValue());
if (isNative && success) 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 ilcArgsValue,
string ilcPathValue, string ilcPathValue,
string ilcSdkPathValue, string ilcSdkPathValue,
bool isCppMode) string appDepSdkPathValue,
bool isCppMode,
string cppCompilerFlagsValue)
{ {
var outputPath = GetOutputPath(context, configuration, outputOptionValue); var outputPath = GetOutputPath(context, configuration, outputOptionValue);
var nativeOutputPath = Path.Combine(GetOutputPath(context, configuration, outputOptionValue), "native"); var nativeOutputPath = Path.Combine(GetOutputPath(context, configuration, outputOptionValue), "native");
@ -145,10 +152,17 @@ namespace Microsoft.DotNet.Tools.Compiler
// ILC SDK Path // ILC SDK Path
if (!string.IsNullOrWhiteSpace(ilcSdkPathValue)) if (!string.IsNullOrWhiteSpace(ilcSdkPathValue))
{ {
nativeArgs.Add("--appdepsdk"); nativeArgs.Add("--ilcsdkpath");
nativeArgs.Add(ilcSdkPathValue); nativeArgs.Add(ilcSdkPathValue);
} }
// AppDep SDK Path
if (!string.IsNullOrWhiteSpace(appDepSdkPathValue))
{
nativeArgs.Add("--appdepsdk");
nativeArgs.Add(appDepSdkPathValue);
}
// CodeGen Mode // CodeGen Mode
if(isCppMode) if(isCppMode)
{ {
@ -156,6 +170,12 @@ namespace Microsoft.DotNet.Tools.Compiler
nativeArgs.Add("cpp"); nativeArgs.Add("cpp");
} }
if (!string.IsNullOrWhiteSpace(cppCompilerFlagsValue))
{
nativeArgs.Add("--cppcompilerflags");
nativeArgs.Add(cppCompilerFlagsValue);
}
// Configuration // Configuration
if (configuration != null) if (configuration != null)
{ {
@ -304,6 +324,8 @@ namespace Microsoft.DotNet.Tools.Compiler
compilationOptions.KeyFile = Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile)); compilationOptions.KeyFile = Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile));
} }
var references = new List<string>();
// Add compilation options to the args // Add compilation options to the args
compilerArgs.AddRange(compilationOptions.SerializeToArgs()); compilerArgs.AddRange(compilationOptions.SerializeToArgs());
@ -319,16 +341,49 @@ namespace Microsoft.DotNet.Tools.Compiler
if (projectDependency.Project.Files.SourceFiles.Any()) if (projectDependency.Project.Files.SourceFiles.Any())
{ {
var projectOutputPath = GetProjectOutput(projectDependency.Project, projectDependency.Framework, configuration, outputPath); var projectOutputPath = GetProjectOutput(projectDependency.Project, projectDependency.Framework, configuration, outputPath);
compilerArgs.Add($"--reference:{projectOutputPath}"); references.Add(projectOutputPath);
} }
} }
else else
{ {
compilerArgs.AddRange(dependency.CompilationAssemblies.Select(r => $"--reference:{r.ResolvedPath}")); references.AddRange(dependency.CompilationAssemblies.Select(r => r.ResolvedPath));
} }
compilerArgs.AddRange(dependency.SourceReferences); 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)) if (!AddResources(context.ProjectFile, compilerArgs, intermediateOutputPath))
{ {
return false; return false;
@ -394,10 +449,9 @@ namespace Microsoft.DotNet.Tools.Compiler
if (success && !noHost && compilationOptions.EmitEntryPoint.GetValueOrDefault()) if (success && !noHost && compilationOptions.EmitEntryPoint.GetValueOrDefault())
{ {
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
MakeRunnable(runtimeContext, MakeRunnable(runtimeContext,
outputPath, outputPath,
runtimeContext.CreateExporter(configuration)); libraryExporter);
} }
return PrintSummary(diagnostics, sw, success); return PrintSummary(diagnostics, sw, success);

View file

@ -16,6 +16,10 @@
"Microsoft.Extensions.CommandLineUtils.Sources": { "Microsoft.Extensions.CommandLineUtils.Sources": {
"type": "build", "type": "build",
"version": "1.0.0-*" "version": "1.0.0-*"
},
"Microsoft.Extensions.DependencyModel": {
"type": "build",
"version": "1.0.0-*"
} }
}, },
"frameworks": { "frameworks": {

View file

@ -28,7 +28,9 @@ namespace Microsoft.DotNet.Tools.New
{ {
var thisAssembly = typeof(Program).GetTypeInfo().Assembly; var thisAssembly = typeof(Program).GetTypeInfo().Assembly;
var resources = from resourceName in thisAssembly.GetManifestResourceNames() 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; select resourceName;
var resourceNameToFileName = new Dictionary<string, string>(); var resourceNameToFileName = new Dictionary<string, string>();

View 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>

View file

@ -13,20 +13,46 @@ namespace Microsoft.DotNet.Tools.Run
{ {
DebugHelper.HandleDebugSwitch(ref args); DebugHelper.HandleDebugSwitch(ref args);
var help = false;
string helpText = null;
var returnCode = 0;
RunCommand runCmd = new RunCommand(); RunCommand runCmd = new RunCommand();
ArgumentSyntax.Parse(args, syntax => try
{ {
syntax.HandleErrors = false; ArgumentSyntax.Parse(args, syntax =>
syntax.DefineOption("f|framework", ref runCmd.Framework, "Compile a specific framework"); {
syntax.DefineOption("c|configuration", ref runCmd.Configuration, "Configuration under which to build"); syntax.HandleHelp = false;
syntax.DefineOption("t|preserve-temporary", ref runCmd.PreserveTemporary, "Keep the output's temporary directory around"); syntax.HandleErrors = false;
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");
// TODO: this is not supporting args which can be switches (i.e. --test) syntax.DefineOption("f|framework", ref runCmd.Framework, "Compile a specific framework");
// TODO: we need to make a change in System.CommandLine or parse args ourselves. syntax.DefineOption("c|configuration", ref runCmd.Configuration, "Configuration under which to build");
syntax.DefineParameterList("args", ref runCmd.Args, "Arguments to pass to the executable or script"); 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 try
{ {

View 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; }
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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";
}
}

View file

@ -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)
);
}
}
}

View file

@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.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; }
}
}

View file

@ -6,12 +6,11 @@
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>482b1045-a1fa-4063-a0d9-a8107a91a016</ProjectGuid> <ProjectGuid>688870c8-9843-4f9e-8576-d39290ad0f25</ProjectGuid>
<RootNamespace>Microsoft.Extensions.DependencyModel</RootNamespace> <RootNamespace>Microsoft.Extensions.DependencyModel</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> <OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
</PropertyGroup> </PropertyGroup>

View 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\""
]
}
}

View file

@ -6,7 +6,8 @@
"url": "git://github.com/dotnet/cli" "url": "git://github.com/dotnet/cli"
}, },
"compilationOptions": { "compilationOptions": {
"warningsAsErrors": true "warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
}, },
"dependencies": { "dependencies": {
"Newtonsoft.Json": "7.0.1", "Newtonsoft.Json": "7.0.1",

View file

@ -10,14 +10,16 @@ include_directories(inc)
set(SOURCES set(SOURCES
src/args.cpp src/args.cpp
src/main.cpp src/main.cpp
src/tpafile.cpp src/deps_resolver.cpp
src/trace.cpp src/trace.cpp
src/utils.cpp src/utils.cpp
src/coreclr.cpp src/coreclr.cpp
src/servicing_index.cpp
inc/args.h inc/args.h
inc/pal.h inc/pal.h
inc/tpafile.h inc/servicing_index.h
inc/deps_resolver.h
inc/trace.h inc/trace.h
inc/coreclr.h inc/coreclr.h
inc/utils.h) inc/utils.h)

View file

@ -4,14 +4,20 @@
#ifndef ARGS_H #ifndef ARGS_H
#define ARGS_H #define ARGS_H
#include "utils.h"
#include "pal.h" #include "pal.h"
#include "trace.h" #include "trace.h"
struct arguments_t struct arguments_t
{ {
pal::string_t own_path; 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 managed_application;
pal::string_t clr_path;
int app_argc; int app_argc;
const pal::char_t** app_argv; const pal::char_t** app_argv;

View 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

View file

@ -12,6 +12,9 @@
#include <cstring> #include <cstring>
#include <cstdarg> #include <cstdarg>
#include <tuple> #include <tuple>
#include <unordered_map>
#include <memory>
#include <algorithm>
#if defined(_WIN32) #if defined(_WIN32)
@ -65,7 +68,13 @@ namespace pal
typedef wchar_t char_t; typedef wchar_t char_t;
typedef std::wstring string_t; typedef std::wstring string_t;
typedef std::wstringstream stringstream_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 HRESULT hresult_t;
typedef HMODULE dll_t; typedef HMODULE dll_t;
typedef FARPROC proc_t; typedef FARPROC proc_t;
@ -77,11 +86,14 @@ namespace pal
pal::string_t to_palstring(const std::string& str); pal::string_t to_palstring(const std::string& str);
std::string to_stdstring(const pal::string_t& 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 #else
typedef char char_t; typedef char char_t;
typedef std::string string_t; typedef std::string string_t;
typedef std::stringstream stringstream_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 int hresult_t;
typedef void* dll_t; typedef void* dll_t;
typedef void* proc_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 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 pal::string_t to_palstring(const std::string& str) { return str; }
inline std::string to_stdstring(const pal::string_t& 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 #endif
bool realpath(string_t* path);
bool realpath(string_t& path);
bool file_exists(const 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 get_own_executable_path(string_t* recv);
bool getenv(const char_t* name, string_t& recv); bool getenv(const char_t* name, string_t* recv);
bool get_default_packages_directory(string_t& recv); bool get_default_packages_directory(string_t* recv);
bool is_path_rooted(const string_t& path); bool is_path_rooted(const string_t& path);
int xtoi(const char_t* input); 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); proc_t get_symbol(dll_t library, const char* name);
void unload_library(dll_t library); void unload_library(dll_t library);
bool find_coreclr(pal::string_t& recv); bool find_coreclr(pal::string_t* recv);
} }
#endif // PAL_H #endif // PAL_H

View 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;
};

View file

@ -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

View file

@ -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_executable(const pal::string_t& filename);
pal::string_t get_directory(const pal::string_t& path); pal::string_t get_directory(const pal::string_t& path);
pal::string_t get_filename(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); 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 #endif

View file

@ -3,10 +3,12 @@
#include "args.h" #include "args.h"
#include "utils.h" #include "utils.h"
#include "coreclr.h"
arguments_t::arguments_t() : arguments_t::arguments_t() :
managed_application(_X("")), managed_application(_X("")),
clr_path(_X("")), own_path(_X("")),
app_dir(_X("")),
app_argc(0), app_argc(0),
app_argv(nullptr) app_argv(nullptr)
{ {
@ -24,10 +26,22 @@ void display_help()
bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args) bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args)
{ {
// Get the full name of the application // Read trace environment variable
if (!pal::get_own_executable_path(args.own_path) || !pal::realpath(args.own_path)) 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; return false;
} }
@ -43,6 +57,12 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg
return false; return false;
} }
args.managed_application = pal::string_t(argv[1]); 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_argc = argc - 2;
args.app_argv = &argv[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(get_executable(own_name));
managed_app.append(_X(".dll")); managed_app.append(_X(".dll"));
args.managed_application = managed_app; 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_argv = &argv[1];
args.app_argc = argc - 1; args.app_argc = argc - 1;
} }
// Read trace environment variable pal::getenv(_X("DOTNET_PACKAGES"), &args.dotnet_packages);
pal::string_t trace_str; pal::getenv(_X("DOTNET_PACKAGES_CACHE"), &args.dotnet_packages_cache);
if (pal::getenv(_X("COREHOST_TRACE"), trace_str)) pal::getenv(_X("DOTNET_SERVICING"), &args.dotnet_servicing);
{ pal::getenv(_X("DOTNET_RUNTIME_SERVICING"), &args.dotnet_runtime_servicing);
auto trace_val = pal::xtoi(trace_str.c_str()); pal::getenv(_X("DOTNET_HOME"), &args.dotnet_home);
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);
}
}
return true; return true;
} }

View file

@ -41,9 +41,9 @@ bool coreclr::bind(const pal::string_t& libcoreclr_path)
assert(g_coreclr == nullptr); assert(g_coreclr == nullptr);
pal::string_t coreclr_dll_path(libcoreclr_path); 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; return false;
} }

View 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;
}

View file

@ -4,121 +4,191 @@
#include "pal.h" #include "pal.h"
#include "args.h" #include "args.h"
#include "trace.h" #include "trace.h"
#include "tpafile.h" #include "deps_resolver.h"
#include "utils.h" #include "utils.h"
#include "coreclr.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 // App dir should contain coreclr, so skip appending path.
auto ext_location = app_name.find_last_of('.'); pal::string_t cur_dir = *dirs[i];
if (ext_location != std::string::npos) if (dirs[i] != &args.app_dir)
{ {
tpapath.append(app_name.substr(0, ext_location)); 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 // Add packages directory
pal::string_t packages_dir; pal::string_t packages_dir = args.dotnet_packages;
if (!pal::get_default_packages_directory(packages_dir)) if (!pal::directory_exists(packages_dir))
{ {
trace::info(_X("did not find local packages directory")); (void)pal::get_default_packages_directory(&packages_dir);
// We can continue, the app may have it's dependencies locally
} }
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()); return StatusCode::ResolverResolveFailure;
tpa.add_package_dir(packages_dir);
} }
// 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 // Build CoreCLR properties
const char* property_keys[] = { const char* property_keys[] = {
"TRUSTED_PLATFORM_ASSEMBLIES", "TRUSTED_PLATFORM_ASSEMBLIES",
"APP_PATHS", "APP_PATHS",
"APP_NI_PATHS", "APP_NI_PATHS",
"NATIVE_DLL_SEARCH_DIRECTORIES", "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 tpa_paths_cstr = pal::to_stdstring(probe_paths.tpa);
auto app_base_cstr = pal::to_stdstring(app_base); auto app_base_cstr = pal::to_stdstring(args.app_dir);
auto search_paths_cstr = pal::to_stdstring(search_paths); auto native_dirs_cstr = pal::to_stdstring(probe_paths.native);
auto culture_dirs_cstr = pal::to_stdstring(probe_paths.culture);
const char* property_values[] = { const char* property_values[] = {
// TRUSTED_PLATFORM_ASSEMBLIES // TRUSTED_PLATFORM_ASSEMBLIES
tpa_cstr.c_str(), tpa_paths_cstr.c_str(),
// APP_PATHS // APP_PATHS
app_base_cstr.c_str(), app_base_cstr.c_str(),
// APP_NI_PATHS // APP_NI_PATHS
app_base_cstr.c_str(), app_base_cstr.c_str(),
// NATIVE_DLL_SEARCH_DIRECTORIES // NATIVE_DLL_SEARCH_DIRECTORIES
search_paths_cstr.c_str(), native_dirs_cstr.c_str(),
// PLATFORM_RESOURCE_ROOTS
culture_dirs_cstr.c_str(),
// AppDomainCompatSwitch // AppDomainCompatSwitch
"UseLatestBehaviorWhenTFMNotSpecified" "UseLatestBehaviorWhenTFMNotSpecified",
// SERVER_GC
"1"
}; };
// Dump TPA list size_t property_size = sizeof(property_keys) / sizeof(property_keys[0]);
trace::verbose(_X("TPA List: %s"), tpalist.c_str());
// Dump native search paths
trace::verbose(_X("Native Paths: %s"), search_paths.c_str());
// Bind CoreCLR // Bind CoreCLR
if (!coreclr::bind(args.clr_path)) if (!coreclr::bind(clr_path))
{ {
trace::error(_X("failed to bind to coreclr")); trace::error(_X("Failed to bind to coreclr"));
return 1; 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 // Initialize CoreCLR
coreclr::host_handle_t host_handle; coreclr::host_handle_t host_handle;
coreclr::domain_id_t domain_id; coreclr::domain_id_t domain_id;
auto hr = coreclr::initialize( auto hr = coreclr::initialize(
pal::to_stdstring(args.own_path).c_str(), own_path.c_str(),
"clrhost", "clrhost",
property_keys, property_keys,
property_values, property_values,
sizeof(property_keys) / sizeof(property_keys[0]), property_size,
&host_handle, &host_handle,
&domain_id); &domain_id);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
trace::error(_X("failed to initialize CoreCLR, HRESULT: 0x%X"), hr); trace::error(_X("Failed to initialize CoreCLR, HRESULT: 0x%X"), hr);
return 1; return StatusCode::CoreClrInitFailure;
} }
// Convert the args (probably not the most performant way to do this...) if (trace::is_enabled())
auto argv_strs = new std::string[args.app_argc]; {
auto argv = new const char*[args.app_argc]; 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++) 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(); 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( hr = coreclr::execute_assembly(
host_handle, host_handle,
domain_id, domain_id,
args.app_argc, argv.size(),
argv, argv.data(),
pal::to_stdstring(args.managed_application).c_str(), pal::to_stdstring(args.managed_application).c_str(),
&exit_code); &exit_code);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
trace::error(_X("failed to execute managed app, HRESULT: 0x%X"), hr); trace::error(_X("Failed to execute managed app, HRESULT: 0x%X"), hr);
return 1; return StatusCode::CoreClrExeFailure;
} }
// Shut down the CoreCLR // Shut down the CoreCLR
hr = coreclr::shutdown(host_handle, domain_id); hr = coreclr::shutdown(host_handle, domain_id);
if (!SUCCEEDED(hr)) 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(); 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[]) int main(const int argc, const pal::char_t* argv[])
#endif #endif
{ {
// Take care of arguments
arguments_t args; arguments_t args;
if (!parse_arguments(argc, argv, args)) if (!parse_arguments(argc, argv, args))
{ {
return 1; return StatusCode::InvalidArgFailure;
} }
// Resolve paths // Resolve CLR path
if (!pal::realpath(args.managed_application)) 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()); trace::error(_X("Could not resolve coreclr path"));
return 1; return StatusCode::CoreClrResolveFailure;
} }
trace::info(_X("preparing to launch managed application: %s"), args.managed_application.c_str()); return run(args, clr_path);
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);
} }

View file

@ -5,6 +5,7 @@
#include "utils.h" #include "utils.h"
#include "trace.h" #include "trace.h"
#include <cassert>
#include <dlfcn.h> #include <dlfcn.h>
#include <dirent.h> #include <dirent.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -19,7 +20,7 @@
#define symlinkEntrypointExecutable "/proc/curproc/exe" #define symlinkEntrypointExecutable "/proc/curproc/exe"
#endif #endif
bool pal::find_coreclr(pal::string_t& recv) bool pal::find_coreclr(pal::string_t* recv)
{ {
pal::string_t candidate; pal::string_t candidate;
pal::string_t test; pal::string_t test;
@ -28,24 +29,24 @@ bool pal::find_coreclr(pal::string_t& recv)
// TODO: These paths should be consistent // TODO: These paths should be consistent
candidate.assign("/usr/share/dotnet/runtime/coreclr"); candidate.assign("/usr/share/dotnet/runtime/coreclr");
if (coreclr_exists_in_dir(candidate)) { if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate); recv->assign(candidate);
return true; return true;
} }
candidate.assign("/usr/local/share/dotnet/runtime/coreclr"); candidate.assign("/usr/local/share/dotnet/runtime/coreclr");
if (coreclr_exists_in_dir(candidate)) { if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate); recv->assign(candidate);
return true; return true;
} }
return false; 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); *dll = dlopen(path, RTLD_LAZY);
if (dll == nullptr) 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 false;
} }
return true; return true;
@ -56,7 +57,7 @@ pal::proc_t pal::get_symbol(dll_t library, const char* name)
auto result = dlsym(library, name); auto result = dlsym(library, name);
if (result == nullptr) 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; return result;
} }
@ -65,7 +66,7 @@ void pal::unload_library(dll_t library)
{ {
if (dlclose(library) != 0) 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() == '/'; 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)) if (!pal::getenv("HOME", recv))
{ {
return false; return false;
} }
append_path(recv, _X(".dnx")); append_path(&*recv, _X(".dnx"));
append_path(recv, _X("packages")); append_path(&*recv, _X("packages"));
return true; return true;
} }
#if defined(__APPLE__) #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; uint32_t path_length = 0;
if (_NSGetExecutablePath(nullptr, &path_length) == -1) 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]; char path_buf[path_length];
if (_NSGetExecutablePath(path_buf, &path_length) == 0) if (_NSGetExecutablePath(path_buf, &path_length) == 0)
{ {
recv.assign(path_buf); recv->assign(path_buf);
return true; return true;
} }
} }
return false; return false;
} }
#else #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 // Just return the symlink to the exe from /proc
// We'll call realpath on it later // We'll call realpath on it later
recv.assign(symlinkEntrypointExecutable); recv->assign(symlinkEntrypointExecutable);
return true; return true;
} }
#endif #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); auto result = ::getenv(name);
if (result != nullptr) 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, // 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; return true;
} }
bool pal::realpath(pal::string_t& path) bool pal::realpath(pal::string_t* path)
{ {
pal::char_t buf[PATH_MAX]; pal::char_t buf[PATH_MAX];
auto resolved = ::realpath(path.c_str(), buf); auto resolved = ::realpath(path->c_str(), buf);
if (resolved == nullptr) if (resolved == nullptr)
{ {
if (errno == ENOENT) if (errno == ENOENT)
@ -141,19 +143,25 @@ bool pal::realpath(pal::string_t& path)
perror("realpath()"); perror("realpath()");
return false; return false;
} }
path.assign(resolved); path->assign(resolved);
return true; return true;
} }
bool pal::file_exists(const pal::string_t& path) bool pal::file_exists(const pal::string_t& path)
{ {
if (path.empty())
{
return false;
}
struct stat buffer; struct stat buffer;
return (::stat(path.c_str(), &buffer) == 0); 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()); auto dir = opendir(path.c_str());
if (dir != nullptr) 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)); files.push_back(pal::string_t(entry->d_name));
} }
} }
return files;
} }

View file

@ -5,23 +5,24 @@
#include "trace.h" #include "trace.h"
#include "utils.h" #include "utils.h"
#include <cassert>
#include <locale> #include <locale>
#include <codecvt> #include <codecvt>
static std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> g_converter; 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 candidate;
pal::string_t test; pal::string_t test;
// Try %LocalAppData%\dotnet // Try %LocalAppData%\dotnet
if (pal::getenv(_X("LocalAppData"), candidate)) { if (pal::getenv(_X("LocalAppData"), &candidate)) {
append_path(candidate, _X("dotnet")); append_path(&candidate, _X("dotnet"));
append_path(candidate, _X("runtime")); append_path(&candidate, _X("runtime"));
append_path(candidate, _X("coreclr")); append_path(&candidate, _X("coreclr"));
if (coreclr_exists_in_dir(candidate)) { if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate); recv->assign(candidate);
return true; return true;
} }
} }
@ -30,12 +31,12 @@ bool pal::find_coreclr(pal::string_t& recv)
return false; 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); *dll = ::LoadLibraryW(path);
if (dll == nullptr) 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; return false;
} }
@ -43,15 +44,15 @@ bool pal::load_library(const char_t* path, dll_t& dll)
HMODULE dummy_module; HMODULE dummy_module;
if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path, &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; return false;
} }
if (trace::is_enabled()) if (trace::is_enabled())
{ {
pal::char_t buf[PATH_MAX]; pal::char_t buf[PATH_MAX];
::GetModuleFileNameW(dll, buf, PATH_MAX); ::GetModuleFileNameW(*dll, buf, PATH_MAX);
trace::info(_X("loaded library from %s"), buf); trace::info(_X("Loaded library from %s"), buf);
} }
return true; 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. // 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)) if (!pal::getenv(_X("USERPROFILE"), recv))
{ {
return false; return false;
} }
append_path(recv, _X(".dnx")); append_path(&*recv, _X(".dnx"));
append_path(recv, _X("packages")); append_path(&*recv, _X("packages"));
return true; return true;
} }
@ -83,7 +85,7 @@ bool pal::is_path_rooted(const string_t& path)
return path.length() >= 2 && path[1] == L':'; 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); auto length = ::GetEnvironmentVariableW(name, nullptr, 0);
if (length == 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 // Leave the receiver empty and return success
return true; 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; return false;
} }
auto buf = new char_t[length]; auto buf = new char_t[length];
if (::GetEnvironmentVariableW(name, buf, length) == 0) 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; return false;
} }
recv.assign(buf); recv->assign(buf);
delete[] buf; delete[] buf;
return true; return true;
@ -115,14 +117,14 @@ int pal::xtoi(const char_t* input)
return ::_wtoi(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]; char_t program_path[MAX_PATH];
DWORD dwModuleFileName = ::GetModuleFileNameW(NULL, program_path, MAX_PATH); DWORD dwModuleFileName = ::GetModuleFileNameW(NULL, program_path, MAX_PATH);
if (dwModuleFileName == 0 || dwModuleFileName >= MAX_PATH) { if (dwModuleFileName == 0 || dwModuleFileName >= MAX_PATH) {
return false; return false;
} }
recv.assign(program_path); recv->assign(program_path);
return true; return true;
} }
@ -136,21 +138,36 @@ pal::string_t pal::to_palstring(const std::string& str)
return g_converter.from_bytes(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]; 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) 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; return false;
} }
path.assign(buf); path->assign(buf);
return true; return true;
} }
bool pal::file_exists(const string_t& path) bool pal::file_exists(const string_t& path)
{ {
if (path.empty())
{
return false;
}
WIN32_FIND_DATAW data; WIN32_FIND_DATAW data;
auto find_handle = ::FindFirstFileW(path.c_str(), &data); auto find_handle = ::FindFirstFileW(path.c_str(), &data);
bool found = find_handle != INVALID_HANDLE_VALUE; bool found = find_handle != INVALID_HANDLE_VALUE;
@ -158,9 +175,11 @@ bool pal::file_exists(const string_t& path)
return found; 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); string_t search_string(path);
search_string.push_back(DIR_SEPARATOR); 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); files.push_back(filepath);
} while (::FindNextFileW(handle, &data)); } while (::FindNextFileW(handle, &data));
::FindClose(handle); ::FindClose(handle);
return files;
} }

View 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;
}

View file

@ -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);
}

View file

@ -7,7 +7,7 @@
bool coreclr_exists_in_dir(const pal::string_t& candidate) bool coreclr_exists_in_dir(const pal::string_t& candidate)
{ {
pal::string_t test(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()); trace::verbose(_X("checking for CoreCLR in default location: %s"), test.c_str());
return pal::file_exists(test); 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)); (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)) if (pal::is_path_rooted(path2))
{ {
path1.assign(path2); path1->assign(path2);
} }
else 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); 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;
}
}

View file

@ -69,12 +69,6 @@ namespace ConsoleApplication
[Fact] [Fact]
public void TestDotnetCompileNativeCpp() public void TestDotnetCompileNativeCpp()
{ {
// Skip this test on windows
if(SkipForOS(OSPlatform.Windows, "https://github.com/dotnet/cli/issues/335"))
{
return;
}
TestSetup(); TestSetup();
TestRunCommand("dotnet", $"compile --native --cpp -o {OutputDirectory}"); TestRunCommand("dotnet", $"compile --native --cpp -o {OutputDirectory}");
@ -91,6 +85,7 @@ namespace ConsoleApplication
TestRunCommand("dotnet", $"run"); TestRunCommand("dotnet", $"run");
} }
[Fact]
public void TestDotnetPack() public void TestDotnetPack()
{ {
TestSetup(); TestSetup();

View file

@ -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();
}
}
}

View file

@ -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>

View 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": { }
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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));
}
}
}

View file

@ -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);
}
}
}

View file

@ -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}";
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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
{
}
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -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,
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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)) { }
}
}
}

View 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();
}
}
}
}

View 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": { }
}
}

View file

@ -8,9 +8,10 @@ namespace TestApp
{ {
public class Program public class Program
{ {
public static void Main(string[] args) public static int Main(string[] args)
{ {
Console.WriteLine(TestLibrary.Helper.GetMessage()); Console.WriteLine(TestLibrary.Helper.GetMessage());
return 100;
} }
} }
} }

View file

@ -0,0 +1,13 @@
using System;
namespace ConsoleApplication
{
public class Program
{
public static int Main(string[] args)
{
Console.WriteLine("Hello World!");
return 100;
}
}
}

View 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": { }
}
}

View file

@ -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