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.
2. git (available from http://www.git-scm.com/) on the PATH.
3. clang (available from http://clang.llvm.org) on the PATH.
### For OS X
1. Xcode
@ -37,7 +38,7 @@ In order to build .NET Command Line Interface, you need the following installed
##Adding a Command
The donet CLI considers any executable on the path named `dotnet-{commandName}` to be a command it can call out to. `dotnet publish`, for example, is added to the path as an executable called `dotnet-publish`. To add a new command we must create the executable and then add it to the distribution packages for installation.
The dotnet CLI considers any executable on the path named `dotnet-{commandName}` to be a command it can call out to. `dotnet publish`, for example, is added to the path as an executable called `dotnet-publish`. To add a new command we must create the executable and then add it to the distribution packages for installation.
0. Create an issue on https://github.com/dotnet/cli and get consensus on the need for and behavior of the command.
1. Add a new project for the command.
@ -63,4 +64,4 @@ Each command's project root should contain a manpage-style Readme.md that descri
#### Add command to packages
- Update the `symlinks` property of `packaging/debian/debian_config.json` to include the new command
- Update the `$Projects` property in `packaging/osx/scripts/postinstall`
- Update the `$Projects` property in `packaging/osx/scripts/postinstall`

View file

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

View file

@ -59,18 +59,18 @@ Compiling to IL is done using:
dotnet compile
This will drop a binary in `./bin/[configuration]/[framework]/[binary name]` that you can just run.
Finally, you can also try out native compilation on Windows and Ubuntu and Mac.
**Note:** at this point, only the `helloworld` and `dotnetbot` samples will work with native compilation.
Finally, you can also try out native compilation using RyuJIT as shown below:
dotnet compile --native
On Mac OSX, we currently support the C++ Codegenerator (as shown below) and support for RyuJIT (as exemplified above) is coming soon.
The following command will perform native compilation using the C++ Codegenerator:
dotnet compile --native --cpp
This will drop a native single binary in `./bin/[configuration]/[framework]/native/[binary name]` that you can run.
**Note:** At this point, only the `helloworld` and `dotnetbot` samples will work with native compilation.
For more details, please refer to the [documentation](https://github.com/dotnet/corert/tree/master/Documentation).
Building from source

View file

@ -4,7 +4,8 @@
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
#
# $1 is passed to package to enable deb or pkg packaging
# Set OFFLINE environment variable to build offline
set -e
SOURCE="${BASH_SOURCE[0]}"
@ -27,12 +28,19 @@ do
debug)
export CONFIGURATION=Debug
;;
offline)
export OFFLINE=true
;;
*)
esac
done
[ -z "$CONFIGURATION" ] && CONFIGURATION=Debug
if [ ! -z "$OFFLINE" ]; then
header " - Offline Build - "
fi
# Use a repo-local install directory (but not the artifacts directory because that gets cleaned a lot
export DOTNET_INSTALL_DIR=$DIR/.dotnet_stage0/$RID
[ -d $DOTNET_INSTALL_DIR ] || mkdir -p $DOTNET_INSTALL_DIR

View file

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

View file

@ -4,7 +4,8 @@
#
param(
[string]$Configuration="Debug")
[string]$Configuration="Debug",
[switch]$Offline)
. "$PSScriptRoot\_common.ps1"
@ -35,7 +36,11 @@ if (!$env:DOTNET_BUILD_VERSION) {
}
Write-Host -ForegroundColor Green "*** Building dotnet tools version $($env:DOTNET_BUILD_VERSION) - $Configuration ***"
& "$PSScriptRoot\compile.ps1" -Configuration:$Configuration
if ($Offline)
{
Write-Host -ForegroundColor Yellow " - Offline Build -"
}
& "$PSScriptRoot\compile.ps1" -Configuration:$Configuration -Offline:$Offline
if (!$?) {
Write-Host "Building dotnet tools finished with errors."
Exit 1

View file

@ -3,7 +3,8 @@
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
#
param([string]$Configuration = "Debug")
param([string]$Configuration = "Debug",
[switch]$Offline)
$ErrorActionPreference="Stop"
@ -13,6 +14,36 @@ $ErrorActionPreference="Stop"
$StartPath = $env:PATH
$StartDotNetHome = $env:DOTNET_HOME
function getDnx()
{
$DnxPackage = "dnx-coreclr-win-x64.1.0.0-rc1-update1.nupkg"
$DnxVersion = "1.0.0-rc1-16231"
$DnxDir = "$OutputDir\dnx"
$DnxRoot = "$DnxDir/bin"
# check if the required dnx version is already downloaded
if ((Test-Path "$DnxRoot\dnx.exe")) {
$dnxOut = & "$DnxRoot\dnx.exe" --version
if ($dnxOut -Match $DnxVersion) {
Write-Host "Dnx version - $DnxVersion already downloaded."
return $DnxRoot
}
}
# Download dnx to copy to stage2
Remove-Item -Recurse -Force -ErrorAction Ignore $DnxDir
mkdir -Force "$DnxDir" | Out-Null
Write-Host "Downloading Dnx version - $DnxVersion."
$DnxUrl="https://api.nuget.org/packages/$DnxPackage"
Invoke-WebRequest -UseBasicParsing "$DnxUrl" -OutFile "$DnxDir\dnx.zip"
Add-Type -Assembly System.IO.Compression.FileSystem | Out-Null
[System.IO.Compression.ZipFile]::ExtractToDirectory("$DnxDir\dnx.zip", "$DnxDir")
return $DnxRoot
}
try {
# Check prereqs
@ -23,40 +54,35 @@ Download it from https://www.cmake.org
"@
}
# Install a stage 0
header "Installing dotnet stage 0"
& "$PSScriptRoot\install.ps1"
if (!$?) {
Write-Host "Command failed: $PSScriptRoot\install.ps1"
Exit 1
if($Offline){
Write-Host "Skipping Stage 0, Dnx, and Packages dowlnoad: Offline build"
}
# Put stage 0 on the path
$DotNetTools = $env:DOTNET_INSTALL_DIR
if (!$DotNetTools) {
$DotNetTools = "$($env:LOCALAPPDATA)\Microsoft\dotnet"
else {
# Install a stage 0
header "Installing dotnet stage 0"
& "$PSScriptRoot\install.ps1"
if (!$?) {
Write-Host "Command failed: $PSScriptRoot\install.ps1"
Exit 1
}
# Put stage 0 on the path
$DotNetTools = $env:DOTNET_INSTALL_DIR
if (!$DotNetTools) {
$DotNetTools = "$($env:LOCALAPPDATA)\Microsoft\dotnet"
}
$DnxRoot = getDnx
# Restore packages
header "Restoring packages"
& "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --no-cache
if (!$?) {
Write-Host "Command failed: " "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --no-cache
Exit 1
}
}
# Download dnx to copy to stage2
if ((Test-Path "$DnxDir")) {
Remove-Item -Recurse -Force $DnxDir
}
mkdir "$DnxDir" | Out-Null
$DnxUrl="https://api.nuget.org/packages/dnx-coreclr-win-x64.$DnxVersion.nupkg"
Invoke-WebRequest -UseBasicParsing "$DnxUrl" -OutFile "$DnxDir\dnx.zip"
Add-Type -Assembly System.IO.Compression.FileSystem | Out-Null
[System.IO.Compression.ZipFile]::ExtractToDirectory("$DnxDir\dnx.zip", "$DnxDir")
$DnxRoot = "$DnxDir/bin"
# Restore packages
header "Restoring packages"
& "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64" --no-cache
if (!$?) {
Write-Host "Command failed: " dotnet restore "$RepoRoot" --quiet --runtime "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64" --no-cache
Exit 1
}
header "Building corehost"
pushd "$RepoRoot\src\corehost"
try {
@ -129,20 +155,13 @@ Download it from https://www.cmake.org
if (!$?) {
Write-Host "Command failed: " cmd /c "$PSScriptRoot\build\build_appdeps.cmd" "$Stage2Dir"
Exit 1
}
}
# Smoke test stage2
$env:DOTNET_HOME = "$Stage2Dir"
& "$PSScriptRoot\test\smoke-test.ps1"
# Run tests on stage2 dotnet tools
& "$PSScriptRoot\test\runtests.ps1"
if (!$?) {
Write-Host "Command failed: $PSScriptRoot\test\smoke-test.ps1"
Exit 1
}
# E2E Test of stage2
& "$PSScriptRoot\test\e2e-test.ps1"
if (!$?) {
Write-Host "Command failed: $PSScriptRoot\test\e2e-test.ps1"
Write-Host "Command failed: $PSScriptRoot\test\runtests.ps1"
Exit 1
}

View file

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

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
{
internal struct CommandResult
public struct CommandResult
{
public static readonly CommandResult Empty = new CommandResult();

View file

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

View file

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

View file

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

View file

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

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"),
DelaySign = rawOptions.ValueAsNullableBoolean("delaySign"),
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-*",
"compilationOptions": {
"keyFile": "../../tools/Key.snk"
},
"description": "Types to model a .NET Project",
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616",
@ -19,6 +22,10 @@
"Microsoft.Extensions.HashCodeCombiner.Sources": {
"type": "build",
"version": "1.0.0-*"
},
"Microsoft.Extensions.DependencyModel": {
"type": "build",
"version": "1.0.0-*"
}
},

View file

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

View file

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

View file

@ -17,11 +17,13 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
NativeIntermediateMode? nativeMode = null;
string ilcArgs = null;
string ilcPath = null;
string ilcSdkPath = null;
string appDepSdk = null;
string logPath = null;
var help = false;
string helpText = null;
var returnCode = 0;
string cppCompilerFlags = null;
IReadOnlyList<string> references = Array.Empty<string>();
IReadOnlyList<string> linklib = Array.Empty<string>();
@ -45,7 +47,9 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Custom Extensibility Points to support CoreRT workflow TODO better descriptions
syntax.DefineOption("ilcargs", ref ilcArgs, "Use to specify custom arguments for the IL Compiler.");
syntax.DefineOption("ilcpath", ref ilcPath, "Use to plug in a custom built ilc.exe");
syntax.DefineOption("ilcpath", ref ilcPath, "Use to specify a custom build of IL Compiler.");
syntax.DefineOption("ilcsdkpath", ref ilcSdkPath, "Use to specify a custom build of IL Compiler SDK");
syntax.DefineOptionList("linklib", ref linklib, "Use to link in additional static libs");
// TEMPORARY Hack until CoreRT compatible Framework Libs are available
@ -54,6 +58,9 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Optional Log Path
syntax.DefineOption("logpath", ref logPath, "Use to dump Native Compilation Logs to a file.");
// Optional flags to be passed to the native compiler
syntax.DefineOption("cppcompilerflags", ref cppCompilerFlags, "Additional flags to be passed to the native compiler.");
syntax.DefineOption("h|help", ref help, "Help for compile native.");
syntax.DefineParameter("INPUT_ASSEMBLY", ref inputAssembly,
@ -125,9 +132,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
ReferencePaths = references,
IlcArgs = ilcArgs,
IlcPath = ilcPath,
IlcSdkPath = ilcSdkPath,
LinkLibPaths = linklib,
AppDepSDKPath = appDepSdk,
LogPath = logPath
LogPath = logPath,
CppCompilerFlags = cppCompilerFlags
};
}
}

View file

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

View file

@ -19,7 +19,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
private readonly string cLibsFlags = "-lm -ldl";
private readonly string cflags = "-g -lstdc++ -lrt -Wno-invalid-offsetof -pthread";
private readonly string[] libs = new string[]
private readonly string[] IlcSdkLibs = new string[]
{
"libbootstrappercpp.a",
"libPortableRuntime.a",
@ -65,24 +65,29 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Flags
argsList.Add(cflags);
// Add Includes
// TODO: Enable this when https://github.com/dotnet/cli/pull/469 goes through.
// var ilcSdkIncPath = Path.Combine(config.IlcSdkPath, "inc");
//
// Get the directory name to ensure there are no trailing slashes as they may conflict
// with the terminating " we suffix to account for paths with spaces in them.
var ilcSdkIncPath = Path.GetDirectoryName(config.IlcSdkPath);
argsList.Add("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/ubuntu.14.04"));
argsList.Add("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk"));
argsList.Add($"\"{ilcSdkIncPath}\"");
// Input File
var inCppFile = DetermineInFile(config);
argsList.Add(inCppFile);
// Add Stubs
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/ubuntu.14.04/lxstubs.cpp"));
// Libs
foreach (var lib in libs)
// Pass the optional native compiler flags if specified
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
{
var libPath = Path.Combine(config.IlcPath, lib);
argsList.Add(config.CppCompilerFlags);
}
// ILC SDK Libs
foreach (var lib in IlcSdkLibs)
{
var libPath = Path.Combine(config.IlcSdkPath, lib);
argsList.Add(libPath);
}

View file

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

View file

@ -21,7 +21,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Link to iconv APIs
private readonly string libFlags = "-liconv";
private readonly string[] libs = new string[]
private readonly string[] IlcSdkLibs = new string[]
{
"libbootstrappercpp.a",
"libPortableRuntime.a",
@ -67,27 +67,32 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Flags
argsList.Add(cflags);
// Add Includes
// TODO: Enable this when https://github.com/dotnet/cli/pull/469 goes through.
// var ilcSdkIncPath = Path.Combine(config.IlcSdkPath, "inc");
//
// Get the directory name to ensure there are no trailing slashes as they may conflict
// with the terminating " we suffix to account for paths with spaces in them.
var ilcSdkIncPath = Path.GetDirectoryName(config.IlcSdkPath);
argsList.Add("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10"));
argsList.Add("-I");
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk"));
argsList.Add($"\"{ilcSdkIncPath}\"");
// Input File
var inCppFile = DetermineInFile(config);
argsList.Add(inCppFile);
// Add Stubs
argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/osx.10.10/osxstubs.cpp"));
// Lib flags
argsList.Add(libFlags);
// Libs
foreach (var lib in libs)
// Pass the optional native compiler flags if specified
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
{
var libPath = Path.Combine(config.IlcPath, lib);
argsList.Add(config.CppCompilerFlags);
}
// ILC SDK Libs
foreach (var lib in IlcSdkLibs)
{
var libPath = Path.Combine(config.IlcSdkPath, lib);
// Forward the library to linked to the linker
argsList.Add("-Xlinker");

View file

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

View file

@ -22,7 +22,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
private static readonly Dictionary<BuildConfiguration, string> ConfigurationCompilerOptionsMap = new Dictionary<BuildConfiguration, string>
{
{ BuildConfiguration.debug, "/ZI /nologo /W3 /WX- /sdl /Od /D CPPCODEGEN /D WIN32 /D _DEBUG /D _CONSOLE /D _LIB /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Gd /TP /wd4477 /errorReport:prompt" },
{ BuildConfiguration.debug, "/ZI /nologo /W3 /WX- /sdl /Od /D CPPCODEGEN /D WIN32 /D _CONSOLE /D _LIB /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MD /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Gd /TP /wd4477 /errorReport:prompt" },
{ BuildConfiguration.release, "/Zi /nologo /W3 /WX- /sdl /O2 /Oi /GL /D CPPCODEGEN /D WIN32 /D NDEBUG /D _CONSOLE /D _LIB /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Gd /TP /wd4477 /errorReport:prompt" }
};
@ -68,17 +68,26 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
argsList.Add("/c");
// Add Includes
var win7CppSdkPath = Path.Combine(config.AppDepSDKPath, "CPPSdk\\win7");
//
// TODO: Enable this when https://github.com/dotnet/cli/pull/469 goes through.
// var ilcSdkIncPath = Path.Combine(config.IlcSdkPath, "inc");
//
// Get the directory name to ensure there are no trailing slashes as they may conflict
// with the terminating " we suffix to account for paths with spaces in them.
var ilcSdkIncPath = config.IlcSdkPath;
ilcSdkIncPath = ilcSdkIncPath.TrimEnd(new char[] {'\\'});
argsList.Add("/I");
argsList.Add($"\"{win7CppSdkPath}\"");
var cppSdkPath = Path.Combine(config.AppDepSDKPath, "CPPSdk");
argsList.Add("/I");
argsList.Add($"\"{cppSdkPath}\"");
argsList.Add($"\"{ilcSdkIncPath}\"");
// Configuration Based Compiler Options
argsList.Add(ConfigurationCompilerOptionsMap[config.BuildType]);
// Pass the optional native compiler flags if specified
if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags))
{
argsList.Add(config.CppCompilerFlags);
}
// Output
var objOut = DetermineOutputFile(config);
argsList.Add($"/Fo\"{objOut}\"");

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" }
};
private static readonly Dictionary<NativeIntermediateMode, string[]> ModeLibMap = new Dictionary<NativeIntermediateMode, string[]>
private static readonly Dictionary<NativeIntermediateMode, string[]> IlcSdkLibMap = new Dictionary<NativeIntermediateMode, string[]>
{
{ NativeIntermediateMode.cpp, new string[] { "PortableRuntime.lib", "bootstrappercpp.lib" } },
{ NativeIntermediateMode.ryujit, new string[] { "Runtime.lib", "bootstrapper.lib" } }
@ -46,9 +46,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
"odbccp32.lib"
};
// We will always link against msvcrt.lib since the runtime libraries are also built against msvcrt.lib as we are not interested in assertions
// from CRT code.
private static readonly Dictionary<BuildConfiguration, string[]> ConfigurationLinkLibMap = new Dictionary<BuildConfiguration, string[]>()
{
{ BuildConfiguration.debug , new string[] { "msvcrtd.lib" } },
{ BuildConfiguration.debug , new string[] { "msvcrt.lib" } },
{ BuildConfiguration.release , new string[] { "msvcrt.lib" } }
};
@ -98,11 +100,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
// Constant Libs
argsList.Add(string.Join(" ", ConstantLinkLibs));
// SDK Libs
var SDKLibs = ModeLibMap[config.NativeMode];
// ILC SDK Libs
var SDKLibs = IlcSdkLibMap[config.NativeMode];
foreach (var lib in SDKLibs)
{
var sdkLibPath = Path.Combine(config.IlcPath, lib);
var sdkLibPath = Path.Combine(config.IlcSdkPath, lib);
argsList.Add($"\"{sdkLibPath}\"");
}

View file

@ -14,12 +14,14 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
private string _inputManagedAssemblyPath;
private string _appDepSdkPath;
private string _ilcPath;
private string _ilcSdkPath;
private string _outputDirectory;
private string _intermediateDirectory;
private string _logPath;
private string _ilcArgs;
private readonly List<string> _referencePaths;
private readonly List<string> _linkLibPaths;
private string _cppCompilerFlags;
public string LogPath
{
@ -129,12 +131,44 @@ namespace Microsoft.DotNet.Tools.Compiler.Native
}
}
public string IlcSdkPath
{
get
{
return _ilcSdkPath;
}
set
{
if (!Directory.Exists(value))
{
throw new Exception($"ILC SDK Directory does not exist: {value}.");
}
_ilcSdkPath = value;
}
}
public string CppCompilerFlags
{
get
{
return _cppCompilerFlags;
}
set
{
_cppCompilerFlags = value;
}
}
private NativeCompileSettings()
{
_linkLibPaths = new List<string>();
_referencePaths = new List<string>();
IlcPath = AppContext.BaseDirectory;
// By default, ILC SDK Path is assumed to be the same folder as ILC path
IlcSdkPath = IlcPath;
Architecture = DefaultArchitectureMode;
BuildType = DefaultBuiltType;
NativeMode = DefaultNativeModel;

View file

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

View file

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

View file

@ -16,6 +16,7 @@ using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.DotNet.Tools.Compiler
{
@ -44,8 +45,10 @@ namespace Microsoft.DotNet.Tools.Compiler
var arch = app.Option("-a|--arch <ARCH>", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue);
var ilcArgs = app.Option("--ilcargs <ARGS>", "Command line arguments to be passed directly to ILCompiler.", CommandOptionType.SingleValue);
var ilcPath = app.Option("--ilcpath <PATH>", "Path to the folder containing custom built ILCompiler.", CommandOptionType.SingleValue);
var ilcSdkPath = app.Option("--ilcsdkpath <PATH>", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue);
var ilcSdkPath = app.Option("--ilcsdkpath <PATH>", "Path to the folder containing custom built ILCompiler SDK.", CommandOptionType.SingleValue);
var appDepSdkPath = app.Option("--appdepsdkpath <PATH>", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue);
var cppMode = app.Option("--cpp", "Flag to do native compilation with C++ code generator.", CommandOptionType.NoValue);
var cppCompilerFlags = app.Option("--cppcompilerflags <flags>", "Additional flags to be passed to the native compiler.", CommandOptionType.SingleValue);
app.OnExecute(() =>
{
@ -63,9 +66,11 @@ namespace Microsoft.DotNet.Tools.Compiler
var ilcArgsValue = ilcArgs.Value();
var ilcPathValue = ilcPath.Value();
var ilcSdkPathValue = ilcSdkPath.Value();
var appDepSdkPathValue = appDepSdkPath.Value();
var configValue = configuration.Value() ?? Constants.DefaultConfiguration;
var outputValue = output.Value();
var intermediateValue = intermediateOutput.Value();
var cppCompilerFlagsValue = cppCompilerFlags.Value();
// Load project contexts for each framework and compile them
bool success = true;
@ -77,7 +82,7 @@ namespace Microsoft.DotNet.Tools.Compiler
success &= Compile(context, configValue, outputValue, intermediateValue, buildProjectReferences, noHost.HasValue());
if (isNative && success)
{
success &= CompileNative(context, configValue, outputValue, buildProjectReferences, intermediateValue, archValue, ilcArgsValue, ilcPathValue, ilcSdkPathValue, isCppMode);
success &= CompileNative(context, configValue, outputValue, buildProjectReferences, intermediateValue, archValue, ilcArgsValue, ilcPathValue, ilcSdkPathValue, appDepSdkPathValue, isCppMode, cppCompilerFlagsValue);
}
}
@ -109,7 +114,9 @@ namespace Microsoft.DotNet.Tools.Compiler
string ilcArgsValue,
string ilcPathValue,
string ilcSdkPathValue,
bool isCppMode)
string appDepSdkPathValue,
bool isCppMode,
string cppCompilerFlagsValue)
{
var outputPath = GetOutputPath(context, configuration, outputOptionValue);
var nativeOutputPath = Path.Combine(GetOutputPath(context, configuration, outputOptionValue), "native");
@ -145,10 +152,17 @@ namespace Microsoft.DotNet.Tools.Compiler
// ILC SDK Path
if (!string.IsNullOrWhiteSpace(ilcSdkPathValue))
{
nativeArgs.Add("--appdepsdk");
nativeArgs.Add("--ilcsdkpath");
nativeArgs.Add(ilcSdkPathValue);
}
// AppDep SDK Path
if (!string.IsNullOrWhiteSpace(appDepSdkPathValue))
{
nativeArgs.Add("--appdepsdk");
nativeArgs.Add(appDepSdkPathValue);
}
// CodeGen Mode
if(isCppMode)
{
@ -156,6 +170,12 @@ namespace Microsoft.DotNet.Tools.Compiler
nativeArgs.Add("cpp");
}
if (!string.IsNullOrWhiteSpace(cppCompilerFlagsValue))
{
nativeArgs.Add("--cppcompilerflags");
nativeArgs.Add(cppCompilerFlagsValue);
}
// Configuration
if (configuration != null)
{
@ -304,6 +324,8 @@ namespace Microsoft.DotNet.Tools.Compiler
compilationOptions.KeyFile = Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile));
}
var references = new List<string>();
// Add compilation options to the args
compilerArgs.AddRange(compilationOptions.SerializeToArgs());
@ -319,16 +341,49 @@ namespace Microsoft.DotNet.Tools.Compiler
if (projectDependency.Project.Files.SourceFiles.Any())
{
var projectOutputPath = GetProjectOutput(projectDependency.Project, projectDependency.Framework, configuration, outputPath);
compilerArgs.Add($"--reference:{projectOutputPath}");
references.Add(projectOutputPath);
}
}
else
{
compilerArgs.AddRange(dependency.CompilationAssemblies.Select(r => $"--reference:{r.ResolvedPath}"));
references.AddRange(dependency.CompilationAssemblies.Select(r => r.ResolvedPath));
}
compilerArgs.AddRange(dependency.SourceReferences);
}
compilerArgs.AddRange(references.Select(r => $"--reference:{r}"));
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
var libraryExporter = runtimeContext.CreateExporter(configuration);
if (compilationOptions.PreserveCompilationContext == true)
{
var dependencyContext = DependencyContextBuilder.FromLibraryExporter(
libraryExporter, context.TargetFramework.DotNetFrameworkName, context.RuntimeIdentifier);
var writer = new DependencyContextWriter();
var depsJsonFile = Path.Combine(intermediateOutputPath, context.ProjectFile.Name + "dotnet-compile.deps.json");
using (var fileStream = File.Create(depsJsonFile))
{
writer.Write(dependencyContext, fileStream);
}
compilerArgs.Add($"--resource:\"{depsJsonFile}\",{context.ProjectFile.Name}.deps.json");
var refsFolder = Path.Combine(outputPath, "refs");
if (Directory.Exists(refsFolder))
{
Directory.Delete(refsFolder, true);
}
Directory.CreateDirectory(refsFolder);
foreach (var reference in references)
{
File.Copy(reference, Path.Combine(refsFolder, Path.GetFileName(reference)));
}
}
if (!AddResources(context.ProjectFile, compilerArgs, intermediateOutputPath))
{
return false;
@ -394,10 +449,9 @@ namespace Microsoft.DotNet.Tools.Compiler
if (success && !noHost && compilationOptions.EmitEntryPoint.GetValueOrDefault())
{
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
MakeRunnable(runtimeContext,
outputPath,
runtimeContext.CreateExporter(configuration));
libraryExporter);
}
return PrintSummary(diagnostics, sw, success);

View file

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

View file

@ -28,7 +28,9 @@ namespace Microsoft.DotNet.Tools.New
{
var thisAssembly = typeof(Program).GetTypeInfo().Assembly;
var resources = from resourceName in thisAssembly.GetManifestResourceNames()
where resourceName.ToLowerInvariant().EndsWith(".cs") || resourceName.ToLowerInvariant().EndsWith(".json")
where resourceName.ToLowerInvariant().EndsWith(".cs")
|| resourceName.ToLowerInvariant().EndsWith(".json")
|| resourceName.ToLowerInvariant().EndsWith(".config")
select resourceName;
var resourceNameToFileName = new Dictionary<string, string>();

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);
var help = false;
string helpText = null;
var returnCode = 0;
RunCommand runCmd = new RunCommand();
ArgumentSyntax.Parse(args, syntax =>
try
{
syntax.HandleErrors = false;
syntax.DefineOption("f|framework", ref runCmd.Framework, "Compile a specific framework");
syntax.DefineOption("c|configuration", ref runCmd.Configuration, "Configuration under which to build");
syntax.DefineOption("t|preserve-temporary", ref runCmd.PreserveTemporary, "Keep the output's temporary directory around");
syntax.DefineOption("p|project", ref runCmd.Project, "The path to the project to run (defaults to the current directory). Can be a path to a project.json or a project directory");
ArgumentSyntax.Parse(args, syntax =>
{
syntax.HandleHelp = false;
syntax.HandleErrors = false;
// TODO: this is not supporting args which can be switches (i.e. --test)
// TODO: we need to make a change in System.CommandLine or parse args ourselves.
syntax.DefineParameterList("args", ref runCmd.Args, "Arguments to pass to the executable or script");
});
syntax.DefineOption("f|framework", ref runCmd.Framework, "Compile a specific framework");
syntax.DefineOption("c|configuration", ref runCmd.Configuration, "Configuration under which to build");
syntax.DefineOption("t|preserve-temporary", ref runCmd.PreserveTemporary, "Keep the output's temporary directory around");
syntax.DefineOption("p|project", ref runCmd.Project, "The path to the project to run (defaults to the current directory). Can be a path to a project.json or a project directory");
syntax.DefineOption("h|help", ref help, "Help for compile native.");
// TODO: this is not supporting args which can be switches (i.e. --test)
// TODO: we need to make a change in System.CommandLine or parse args ourselves.
syntax.DefineParameterList("args", ref runCmd.Args, "Arguments to pass to the executable or script");
helpText = syntax.GetHelpText();
});
}
catch (ArgumentSyntaxException exception)
{
Console.Error.WriteLine(exception.Message);
help = true;
returnCode = 1;
}
if (help)
{
Console.WriteLine(helpText);
return returnCode;
}
try
{

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

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"
},
"compilationOptions": {
"warningsAsErrors": true
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
},
"dependencies": {
"Newtonsoft.Json": "7.0.1",

View file

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

View file

@ -4,14 +4,20 @@
#ifndef ARGS_H
#define ARGS_H
#include "utils.h"
#include "pal.h"
#include "trace.h"
struct arguments_t
{
pal::string_t own_path;
pal::string_t app_dir;
pal::string_t dotnet_servicing;
pal::string_t dotnet_runtime_servicing;
pal::string_t dotnet_home;
pal::string_t dotnet_packages;
pal::string_t dotnet_packages_cache;
pal::string_t managed_application;
pal::string_t clr_path;
int app_argc;
const pal::char_t** app_argv;

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 <cstdarg>
#include <tuple>
#include <unordered_map>
#include <memory>
#include <algorithm>
#if defined(_WIN32)
@ -65,7 +68,13 @@ namespace pal
typedef wchar_t char_t;
typedef std::wstring string_t;
typedef std::wstringstream stringstream_t;
typedef std::ifstream ifstream_t;
// TODO: Agree on the correct encoding of the files: The PoR for now is to
// temporarily wchar for Windows and char for Unix. Current implementation
// implicitly expects the contents on both Windows and Unix as char and
// converts them to wchar in code for Windows. This line should become:
// typedef std::basic_ifstream<pal::char_t> ifstream_t.
typedef std::basic_ifstream<char> ifstream_t;
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
typedef HRESULT hresult_t;
typedef HMODULE dll_t;
typedef FARPROC proc_t;
@ -77,11 +86,14 @@ namespace pal
pal::string_t to_palstring(const std::string& str);
std::string to_stdstring(const pal::string_t& str);
void to_palstring(const char* str, pal::string_t* out);
void to_stdstring(const pal::char_t* str, std::string* out);
#else
typedef char char_t;
typedef std::string string_t;
typedef std::stringstream stringstream_t;
typedef std::ifstream ifstream_t;
typedef std::basic_ifstream<char> ifstream_t;
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
typedef int hresult_t;
typedef void* dll_t;
typedef void* proc_t;
@ -92,24 +104,26 @@ namespace pal
inline void err_vprintf(const char_t* format, va_list vl) { ::vfprintf(stderr, format, vl); ::fputc('\n', stderr); }
inline pal::string_t to_palstring(const std::string& str) { return str; }
inline std::string to_stdstring(const pal::string_t& str) { return str; }
inline void to_palstring(const char* str, pal::string_t* out) { out->assign(str); }
inline void to_stdstring(const pal::char_t* str, std::string* out) { out->assign(str); }
#endif
bool realpath(string_t& path);
bool realpath(string_t* path);
bool file_exists(const string_t& path);
std::vector<pal::string_t> readdir(const string_t& path);
inline bool directory_exists(const string_t& path) { return file_exists(path); }
void readdir(const string_t& path, std::vector<pal::string_t>* list);
bool get_own_executable_path(string_t& recv);
bool getenv(const char_t* name, string_t& recv);
bool get_default_packages_directory(string_t& recv);
bool get_own_executable_path(string_t* recv);
bool getenv(const char_t* name, string_t* recv);
bool get_default_packages_directory(string_t* recv);
bool is_path_rooted(const string_t& path);
int xtoi(const char_t* input);
bool load_library(const char_t* path, dll_t& dll);
bool load_library(const char_t* path, dll_t* dll);
proc_t get_symbol(dll_t library, const char* name);
void unload_library(dll_t library);
bool find_coreclr(pal::string_t& recv);
bool find_coreclr(pal::string_t* recv);
}
#endif // PAL_H

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_directory(const pal::string_t& path);
pal::string_t get_filename(const pal::string_t& path);
void append_path(pal::string_t& path1, const pal::char_t* path2);
void append_path(pal::string_t* path1, const pal::char_t* path2);
bool coreclr_exists_in_dir(const pal::string_t& candidate);
void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl);
#endif

View file

@ -3,10 +3,12 @@
#include "args.h"
#include "utils.h"
#include "coreclr.h"
arguments_t::arguments_t() :
managed_application(_X("")),
clr_path(_X("")),
own_path(_X("")),
app_dir(_X("")),
app_argc(0),
app_argv(nullptr)
{
@ -24,10 +26,22 @@ void display_help()
bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args)
{
// Get the full name of the application
if (!pal::get_own_executable_path(args.own_path) || !pal::realpath(args.own_path))
// Read trace environment variable
pal::string_t trace_str;
if (pal::getenv(_X("COREHOST_TRACE"), &trace_str))
{
trace::error(_X("failed to locate current executable"));
auto trace_val = pal::xtoi(trace_str.c_str());
if (trace_val > 0)
{
trace::enable();
trace::info(_X("Tracing enabled"));
}
}
// Get the full name of the application
if (!pal::get_own_executable_path(&args.own_path) || !pal::realpath(&args.own_path))
{
trace::error(_X("Failed to locate current executable"));
return false;
}
@ -43,6 +57,12 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg
return false;
}
args.managed_application = pal::string_t(argv[1]);
if (!pal::realpath(&args.managed_application))
{
trace::error(_X("Failed to locate managed application: %s"), args.managed_application.c_str());
return false;
}
args.app_dir = get_directory(args.managed_application);
args.app_argc = argc - 2;
args.app_argv = &argv[2];
}
@ -54,39 +74,20 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg
managed_app.append(get_executable(own_name));
managed_app.append(_X(".dll"));
args.managed_application = managed_app;
if (!pal::realpath(&args.managed_application))
{
trace::error(_X("Failed to locate managed application: %s"), args.managed_application.c_str());
return false;
}
args.app_dir = own_dir;
args.app_argv = &argv[1];
args.app_argc = argc - 1;
}
// Read trace environment variable
pal::string_t trace_str;
if (pal::getenv(_X("COREHOST_TRACE"), trace_str))
{
auto trace_val = pal::xtoi(trace_str.c_str());
if (trace_val > 0)
{
trace::enable();
trace::info(_X("tracing enabled"));
}
}
// Read CLR path from environment variable
pal::string_t home_str;
pal::getenv(_X("DOTNET_HOME"), home_str);
if (!home_str.empty())
{
append_path(home_str, _X("runtime"));
append_path(home_str, _X("coreclr"));
args.clr_path.assign(home_str);
}
else
{
// Use platform-specific search algorithm
if (pal::find_coreclr(home_str))
{
args.clr_path.assign(home_str);
}
}
pal::getenv(_X("DOTNET_PACKAGES"), &args.dotnet_packages);
pal::getenv(_X("DOTNET_PACKAGES_CACHE"), &args.dotnet_packages_cache);
pal::getenv(_X("DOTNET_SERVICING"), &args.dotnet_servicing);
pal::getenv(_X("DOTNET_RUNTIME_SERVICING"), &args.dotnet_runtime_servicing);
pal::getenv(_X("DOTNET_HOME"), &args.dotnet_home);
return true;
}

View file

@ -41,9 +41,9 @@ bool coreclr::bind(const pal::string_t& libcoreclr_path)
assert(g_coreclr == nullptr);
pal::string_t coreclr_dll_path(libcoreclr_path);
append_path(coreclr_dll_path, LIBCORECLR_NAME);
append_path(&coreclr_dll_path, LIBCORECLR_NAME);
if (!pal::load_library(coreclr_dll_path.c_str(), g_coreclr))
if (!pal::load_library(coreclr_dll_path.c_str(), &g_coreclr))
{
return false;
}

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 "args.h"
#include "trace.h"
#include "tpafile.h"
#include "deps_resolver.h"
#include "utils.h"
#include "coreclr.h"
void get_tpafile_path(const pal::string_t& app_base, const pal::string_t& app_name, pal::string_t& tpapath)
enum StatusCode
{
tpapath.reserve(app_base.length() + app_name.length() + 5);
Failure = 0x87FF0000,
InvalidArgFailure = Failure | 0x1,
CoreClrResolveFailure = Failure | 0x2,
CoreClrBindFailure = Failure | 0x3,
CoreClrInitFailure = Failure | 0x4,
CoreClrExeFailure = Failure | 0x5,
ResolverInitFailure = Failure | 0x6,
ResolverResolveFailure = Failure | 0x7,
};
tpapath.append(app_base);
tpapath.push_back(DIR_SEPARATOR);
// ----------------------------------------------------------------------
// resolve_clr_path: Resolve CLR Path in priority order
//
// Description:
// Check if CoreCLR library exists in runtime servicing dir or app
// local or DOTNET_HOME directory in that order of priority. If these
// fail to locate CoreCLR, then check platform-specific search.
//
// Returns:
// "true" if path to the CoreCLR dir can be resolved in "clr_path"
// parameter. Else, returns "false" with "clr_path" unmodified.
//
bool resolve_clr_path(const arguments_t& args, pal::string_t* clr_path)
{
const pal::string_t* dirs[] = {
&args.dotnet_runtime_servicing, // DOTNET_RUNTIME_SERVICING
&args.app_dir, // APP LOCAL
&args.dotnet_home // DOTNET_HOME
};
for (int i = 0; i < sizeof(dirs) / sizeof(dirs[0]); ++i)
{
if (dirs[i]->empty())
{
continue;
}
// Remove the extension from the app_name
auto ext_location = app_name.find_last_of('.');
if (ext_location != std::string::npos)
{
tpapath.append(app_name.substr(0, ext_location));
// App dir should contain coreclr, so skip appending path.
pal::string_t cur_dir = *dirs[i];
if (dirs[i] != &args.app_dir)
{
append_path(&cur_dir, _X("runtime"));
append_path(&cur_dir, _X("coreclr"));
}
// Found coreclr in priority order.
if (coreclr_exists_in_dir(cur_dir))
{
clr_path->assign(cur_dir);
return true;
}
}
else
// Use platform-specific search algorithm
pal::string_t home_dir = args.dotnet_home;
if (pal::find_coreclr(&home_dir))
{
tpapath.append(app_name);
clr_path->assign(home_dir);
return true;
}
tpapath.append(_X(".deps"));
return false;
}
int run(arguments_t args, pal::string_t app_base, tpafile tpa)
int run(const arguments_t& args, const pal::string_t& clr_path)
{
tpa.add_from_local_dir(app_base);
// Load the deps resolver
deps_resolver_t resolver(args);
if (!resolver.valid())
{
trace::error(_X("Invalid .deps file"));
return StatusCode::ResolverInitFailure;
}
// Add packages directory
pal::string_t packages_dir;
if (!pal::get_default_packages_directory(packages_dir))
pal::string_t packages_dir = args.dotnet_packages;
if (!pal::directory_exists(packages_dir))
{
trace::info(_X("did not find local packages directory"));
// We can continue, the app may have it's dependencies locally
(void)pal::get_default_packages_directory(&packages_dir);
}
else
trace::info(_X("Package directory: %s"), packages_dir.empty() ? _X("not specified") : packages_dir.c_str());
probe_paths_t probe_paths;
if (!resolver.resolve_probe_paths(args.app_dir, packages_dir, args.dotnet_packages_cache, clr_path, &probe_paths))
{
trace::info(_X("using packages directory: %s"), packages_dir.c_str());
tpa.add_package_dir(packages_dir);
return StatusCode::ResolverResolveFailure;
}
// Add native search path
trace::info(_X("using native search path: %s"), packages_dir.c_str());
tpa.add_native_search_path(args.clr_path);
// Build TPA list and search paths
pal::string_t tpalist;
tpa.write_tpa_list(tpalist);
pal::string_t search_paths;
tpa.write_native_paths(search_paths);
// Build CoreCLR properties
const char* property_keys[] = {
"TRUSTED_PLATFORM_ASSEMBLIES",
"APP_PATHS",
"APP_NI_PATHS",
"NATIVE_DLL_SEARCH_DIRECTORIES",
"AppDomainCompatSwitch"
"PLATFORM_RESOURCE_ROOTS",
"AppDomainCompatSwitch",
// TODO: pipe this from corehost.json
"SERVER_GC"
};
auto tpa_cstr = pal::to_stdstring(tpalist);
auto app_base_cstr = pal::to_stdstring(app_base);
auto search_paths_cstr = pal::to_stdstring(search_paths);
auto tpa_paths_cstr = pal::to_stdstring(probe_paths.tpa);
auto app_base_cstr = pal::to_stdstring(args.app_dir);
auto native_dirs_cstr = pal::to_stdstring(probe_paths.native);
auto culture_dirs_cstr = pal::to_stdstring(probe_paths.culture);
const char* property_values[] = {
// TRUSTED_PLATFORM_ASSEMBLIES
tpa_cstr.c_str(),
tpa_paths_cstr.c_str(),
// APP_PATHS
app_base_cstr.c_str(),
// APP_NI_PATHS
app_base_cstr.c_str(),
// NATIVE_DLL_SEARCH_DIRECTORIES
search_paths_cstr.c_str(),
native_dirs_cstr.c_str(),
// PLATFORM_RESOURCE_ROOTS
culture_dirs_cstr.c_str(),
// AppDomainCompatSwitch
"UseLatestBehaviorWhenTFMNotSpecified"
"UseLatestBehaviorWhenTFMNotSpecified",
// SERVER_GC
"1"
};
// Dump TPA list
trace::verbose(_X("TPA List: %s"), tpalist.c_str());
// Dump native search paths
trace::verbose(_X("Native Paths: %s"), search_paths.c_str());
size_t property_size = sizeof(property_keys) / sizeof(property_keys[0]);
// Bind CoreCLR
if (!coreclr::bind(args.clr_path))
if (!coreclr::bind(clr_path))
{
trace::error(_X("failed to bind to coreclr"));
return 1;
trace::error(_X("Failed to bind to coreclr"));
return StatusCode::CoreClrBindFailure;
}
// Verbose logging
if (trace::is_enabled())
{
for (size_t i = 0; i < property_size; ++i)
{
pal::string_t key, val;
pal::to_palstring(property_keys[i], &key);
pal::to_palstring(property_values[i], &val);
trace::verbose(_X("Property %s = %s"), key.c_str(), val.c_str());
}
}
std::string own_path;
pal::to_stdstring(args.own_path.c_str(), &own_path);
// Initialize CoreCLR
coreclr::host_handle_t host_handle;
coreclr::domain_id_t domain_id;
auto hr = coreclr::initialize(
pal::to_stdstring(args.own_path).c_str(),
own_path.c_str(),
"clrhost",
property_keys,
property_values,
sizeof(property_keys) / sizeof(property_keys[0]),
property_size,
&host_handle,
&domain_id);
if (!SUCCEEDED(hr))
{
trace::error(_X("failed to initialize CoreCLR, HRESULT: 0x%X"), hr);
return 1;
trace::error(_X("Failed to initialize CoreCLR, HRESULT: 0x%X"), hr);
return StatusCode::CoreClrInitFailure;
}
// Convert the args (probably not the most performant way to do this...)
auto argv_strs = new std::string[args.app_argc];
auto argv = new const char*[args.app_argc];
if (trace::is_enabled())
{
pal::string_t arg_str;
for (int i = 0; i < args.app_argc; i++)
{
arg_str.append(args.app_argv[i]);
arg_str.append(_X(","));
}
trace::info(_X("Launch host: %s app: %s, argc: %d args: %s"), args.own_path.c_str(),
args.managed_application.c_str(), args.app_argc, arg_str.c_str());
}
// Initialize with empty strings
std::vector<std::string> argv_strs(args.app_argc);
std::vector<const char*> argv(args.app_argc);
for (int i = 0; i < args.app_argc; i++)
{
argv_strs[i] = pal::to_stdstring(pal::string_t(args.app_argv[i]));
pal::to_stdstring(args.app_argv[i], &argv_strs[i]);
argv[i] = argv_strs[i].c_str();
}
@ -127,21 +197,21 @@ int run(arguments_t args, pal::string_t app_base, tpafile tpa)
hr = coreclr::execute_assembly(
host_handle,
domain_id,
args.app_argc,
argv,
argv.size(),
argv.data(),
pal::to_stdstring(args.managed_application).c_str(),
&exit_code);
if (!SUCCEEDED(hr))
{
trace::error(_X("failed to execute managed app, HRESULT: 0x%X"), hr);
return 1;
trace::error(_X("Failed to execute managed app, HRESULT: 0x%X"), hr);
return StatusCode::CoreClrExeFailure;
}
// Shut down the CoreCLR
hr = coreclr::shutdown(host_handle, domain_id);
if (!SUCCEEDED(hr))
{
trace::warning(_X("failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
trace::warning(_X("Failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
}
coreclr::unload();
@ -155,70 +225,19 @@ int __cdecl wmain(const int argc, const pal::char_t* argv[])
int main(const int argc, const pal::char_t* argv[])
#endif
{
// Take care of arguments
arguments_t args;
if (!parse_arguments(argc, argv, args))
{
return 1;
return StatusCode::InvalidArgFailure;
}
// Resolve paths
if (!pal::realpath(args.managed_application))
// Resolve CLR path
pal::string_t clr_path;
if (!resolve_clr_path(args, &clr_path))
{
trace::error(_X("failed to locate managed application: %s"), args.managed_application.c_str());
return 1;
trace::error(_X("Could not resolve coreclr path"));
return StatusCode::CoreClrResolveFailure;
}
trace::info(_X("preparing to launch managed application: %s"), args.managed_application.c_str());
trace::info(_X("host path: %s"), args.own_path.c_str());
pal::string_t argstr;
for (int i = 0; i < args.app_argc; i++)
{
argstr.append(args.app_argv[i]);
argstr.append(_X(","));
}
trace::info(_X("App argc: %d"), args.app_argc);
trace::info(_X("App argv: %s"), argstr.c_str());
auto app_base = get_directory(args.managed_application);
auto app_name = get_filename(args.managed_application);
// App-local coreclr wins
{
pal::string_t candidate;
candidate.assign(app_base);
append_path(candidate, LIBCORECLR_NAME);
if (pal::file_exists(candidate))
{
args.clr_path.assign(app_base);
}
}
if (args.clr_path.empty())
{
trace::error(_X("failed to locate CLR files, set the DOTNET_HOME environment variable"));
return 1;
}
if (!pal::realpath(args.clr_path))
{
trace::error(_X("failed to locate CLR files at %s"), args.clr_path.c_str());
return 1;
}
trace::info(_X("using CLR files from: %s"), args.clr_path.c_str());
trace::info(_X("preparing to launch: %s"), app_name.c_str());
trace::info(_X("using app base: %s"), app_base.c_str());
// Check for and load deps file
pal::string_t tpafile_path;
get_tpafile_path(app_base, app_name, tpafile_path);
trace::info(_X("checking for .deps File at: %s"), tpafile_path.c_str());
tpafile tpa;
if (!tpa.load(tpafile_path))
{
trace::error(_X("invalid .deps file"));
return 1;
}
return run(args, app_base, tpa);
return run(args, clr_path);
}

View file

@ -5,6 +5,7 @@
#include "utils.h"
#include "trace.h"
#include <cassert>
#include <dlfcn.h>
#include <dirent.h>
#include <sys/stat.h>
@ -19,7 +20,7 @@
#define symlinkEntrypointExecutable "/proc/curproc/exe"
#endif
bool pal::find_coreclr(pal::string_t& recv)
bool pal::find_coreclr(pal::string_t* recv)
{
pal::string_t candidate;
pal::string_t test;
@ -28,24 +29,24 @@ bool pal::find_coreclr(pal::string_t& recv)
// TODO: These paths should be consistent
candidate.assign("/usr/share/dotnet/runtime/coreclr");
if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate);
recv->assign(candidate);
return true;
}
candidate.assign("/usr/local/share/dotnet/runtime/coreclr");
if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate);
recv->assign(candidate);
return true;
}
return false;
}
bool pal::load_library(const char_t* path, dll_t& dll)
bool pal::load_library(const char_t* path, dll_t* dll)
{
dll = dlopen(path, RTLD_LAZY);
if (dll == nullptr)
*dll = dlopen(path, RTLD_LAZY);
if (*dll == nullptr)
{
trace::error(_X("failed to load %s, error: %s"), path, dlerror());
trace::error(_X("Failed to load %s, error: %s"), path, dlerror());
return false;
}
return true;
@ -56,7 +57,7 @@ pal::proc_t pal::get_symbol(dll_t library, const char* name)
auto result = dlsym(library, name);
if (result == nullptr)
{
trace::error(_X("failed to resolve library symbol %s, error: %s"), name, dlerror());
trace::error(_X("Failed to resolve library symbol %s, error: %s"), name, dlerror());
}
return result;
}
@ -65,7 +66,7 @@ void pal::unload_library(dll_t library)
{
if (dlclose(library) != 0)
{
trace::warning(_X("failed to unload library, error: %s"), dlerror());
trace::warning(_X("Failed to unload library, error: %s"), dlerror());
}
}
@ -79,19 +80,20 @@ bool pal::is_path_rooted(const pal::string_t& path)
return path.front() == '/';
}
bool pal::get_default_packages_directory(pal::string_t& recv)
bool pal::get_default_packages_directory(pal::string_t* recv)
{
recv->clear();
if (!pal::getenv("HOME", recv))
{
return false;
}
append_path(recv, _X(".dnx"));
append_path(recv, _X("packages"));
append_path(&*recv, _X(".dnx"));
append_path(&*recv, _X("packages"));
return true;
}
#if defined(__APPLE__)
bool pal::get_own_executable_path(pal::string_t& recv)
bool pal::get_own_executable_path(pal::string_t* recv)
{
uint32_t path_length = 0;
if (_NSGetExecutablePath(nullptr, &path_length) == -1)
@ -99,28 +101,28 @@ bool pal::get_own_executable_path(pal::string_t& recv)
char path_buf[path_length];
if (_NSGetExecutablePath(path_buf, &path_length) == 0)
{
recv.assign(path_buf);
recv->assign(path_buf);
return true;
}
}
return false;
}
#else
bool pal::get_own_executable_path(pal::string_t& recv)
bool pal::get_own_executable_path(pal::string_t* recv)
{
// Just return the symlink to the exe from /proc
// We'll call realpath on it later
recv.assign(symlinkEntrypointExecutable);
recv->assign(symlinkEntrypointExecutable);
return true;
}
#endif
bool pal::getenv(const pal::char_t* name, pal::string_t& recv)
bool pal::getenv(const pal::char_t* name, pal::string_t* recv)
{
auto result = ::getenv(name);
if (result != nullptr)
{
recv.assign(result);
recv->assign(result);
}
// We don't return false. Windows does have a concept of an error reading the variable,
@ -128,10 +130,10 @@ bool pal::getenv(const pal::char_t* name, pal::string_t& recv)
return true;
}
bool pal::realpath(pal::string_t& path)
bool pal::realpath(pal::string_t* path)
{
pal::char_t buf[PATH_MAX];
auto resolved = ::realpath(path.c_str(), buf);
auto resolved = ::realpath(path->c_str(), buf);
if (resolved == nullptr)
{
if (errno == ENOENT)
@ -141,19 +143,25 @@ bool pal::realpath(pal::string_t& path)
perror("realpath()");
return false;
}
path.assign(resolved);
path->assign(resolved);
return true;
}
bool pal::file_exists(const pal::string_t& path)
{
if (path.empty())
{
return false;
}
struct stat buffer;
return (::stat(path.c_str(), &buffer) == 0);
}
std::vector<pal::string_t> pal::readdir(const pal::string_t& path)
void pal::readdir(const pal::string_t& path, std::vector<pal::string_t>* list)
{
std::vector<pal::string_t> files;
assert(list != nullptr);
std::vector<pal::string_t>& files = *list;
auto dir = opendir(path.c_str());
if (dir != nullptr)
@ -197,6 +205,4 @@ std::vector<pal::string_t> pal::readdir(const pal::string_t& path)
files.push_back(pal::string_t(entry->d_name));
}
}
return files;
}

View file

@ -5,23 +5,24 @@
#include "trace.h"
#include "utils.h"
#include <cassert>
#include <locale>
#include <codecvt>
static std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> g_converter;
bool pal::find_coreclr(pal::string_t& recv)
bool pal::find_coreclr(pal::string_t* recv)
{
pal::string_t candidate;
pal::string_t test;
// Try %LocalAppData%\dotnet
if (pal::getenv(_X("LocalAppData"), candidate)) {
append_path(candidate, _X("dotnet"));
append_path(candidate, _X("runtime"));
append_path(candidate, _X("coreclr"));
if (pal::getenv(_X("LocalAppData"), &candidate)) {
append_path(&candidate, _X("dotnet"));
append_path(&candidate, _X("runtime"));
append_path(&candidate, _X("coreclr"));
if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate);
recv->assign(candidate);
return true;
}
}
@ -30,12 +31,12 @@ bool pal::find_coreclr(pal::string_t& recv)
return false;
}
bool pal::load_library(const char_t* path, dll_t& dll)
bool pal::load_library(const char_t* path, dll_t* dll)
{
dll = ::LoadLibraryW(path);
if (dll == nullptr)
*dll = ::LoadLibraryW(path);
if (*dll == nullptr)
{
trace::error(_X("failed to load coreclr.dll from %s, HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError()));
trace::error(_X("Failed to load coreclr.dll from %s, HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError()));
return false;
}
@ -43,15 +44,15 @@ bool pal::load_library(const char_t* path, dll_t& dll)
HMODULE dummy_module;
if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path, &dummy_module))
{
trace::error(_X("failed to pin library: %s"));
trace::error(_X("Failed to pin library: %s"));
return false;
}
if (trace::is_enabled())
{
pal::char_t buf[PATH_MAX];
::GetModuleFileNameW(dll, buf, PATH_MAX);
trace::info(_X("loaded library from %s"), buf);
::GetModuleFileNameW(*dll, buf, PATH_MAX);
trace::info(_X("Loaded library from %s"), buf);
}
return true;
@ -67,14 +68,15 @@ void pal::unload_library(dll_t library)
// No-op. On windows, we pin the library, so it can't be unloaded.
}
bool pal::get_default_packages_directory(string_t& recv)
bool pal::get_default_packages_directory(string_t* recv)
{
recv->clear();
if (!pal::getenv(_X("USERPROFILE"), recv))
{
return false;
}
append_path(recv, _X(".dnx"));
append_path(recv, _X("packages"));
append_path(&*recv, _X(".dnx"));
append_path(&*recv, _X("packages"));
return true;
}
@ -83,7 +85,7 @@ bool pal::is_path_rooted(const string_t& path)
return path.length() >= 2 && path[1] == L':';
}
bool pal::getenv(const char_t* name, string_t& recv)
bool pal::getenv(const char_t* name, string_t* recv)
{
auto length = ::GetEnvironmentVariableW(name, nullptr, 0);
if (length == 0)
@ -94,17 +96,17 @@ bool pal::getenv(const char_t* name, string_t& recv)
// Leave the receiver empty and return success
return true;
}
trace::error(_X("failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
trace::error(_X("Failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
return false;
}
auto buf = new char_t[length];
if (::GetEnvironmentVariableW(name, buf, length) == 0)
{
trace::error(_X("failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
trace::error(_X("Failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
return false;
}
recv.assign(buf);
recv->assign(buf);
delete[] buf;
return true;
@ -115,14 +117,14 @@ int pal::xtoi(const char_t* input)
return ::_wtoi(input);
}
bool pal::get_own_executable_path(string_t& recv)
bool pal::get_own_executable_path(string_t* recv)
{
char_t program_path[MAX_PATH];
DWORD dwModuleFileName = ::GetModuleFileNameW(NULL, program_path, MAX_PATH);
if (dwModuleFileName == 0 || dwModuleFileName >= MAX_PATH) {
return false;
}
recv.assign(program_path);
recv->assign(program_path);
return true;
}
@ -136,21 +138,36 @@ pal::string_t pal::to_palstring(const std::string& str)
return g_converter.from_bytes(str);
}
bool pal::realpath(string_t& path)
void pal::to_palstring(const char* str, pal::string_t* out)
{
out->assign(g_converter.from_bytes(str));
}
void pal::to_stdstring(const pal::char_t* str, std::string* out)
{
out->assign(g_converter.to_bytes(str));
}
bool pal::realpath(string_t* path)
{
char_t buf[MAX_PATH];
auto res = ::GetFullPathNameW(path.c_str(), MAX_PATH, buf, nullptr);
auto res = ::GetFullPathNameW(path->c_str(), MAX_PATH, buf, nullptr);
if (res == 0 || res > MAX_PATH)
{
trace::error(_X("error resolving path: %s"), path.c_str());
trace::error(_X("Error resolving path: %s"), path->c_str());
return false;
}
path.assign(buf);
path->assign(buf);
return true;
}
bool pal::file_exists(const string_t& path)
{
if (path.empty())
{
return false;
}
WIN32_FIND_DATAW data;
auto find_handle = ::FindFirstFileW(path.c_str(), &data);
bool found = find_handle != INVALID_HANDLE_VALUE;
@ -158,9 +175,11 @@ bool pal::file_exists(const string_t& path)
return found;
}
std::vector<pal::string_t> pal::readdir(const string_t& path)
void pal::readdir(const string_t& path, std::vector<pal::string_t>* list)
{
std::vector<string_t> files;
assert(list != nullptr);
std::vector<string_t>& files = *list;
string_t search_string(path);
search_string.push_back(DIR_SEPARATOR);
@ -174,6 +193,4 @@ std::vector<pal::string_t> pal::readdir(const string_t& path)
files.push_back(filepath);
} while (::FindNextFileW(handle, &data));
::FindClose(handle);
return files;
}

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)
{
pal::string_t test(candidate);
append_path(test, LIBCORECLR_NAME);
append_path(&test, LIBCORECLR_NAME);
trace::verbose(_X("checking for CoreCLR in default location: %s"), test.c_str());
return pal::file_exists(test);
}
@ -18,19 +18,19 @@ bool ends_with(const pal::string_t& value, const pal::string_t& suffix)
(0 == value.compare(value.length() - suffix.length(), suffix.length(), suffix));
}
void append_path(pal::string_t& path1, const pal::char_t* path2)
void append_path(pal::string_t* path1, const pal::char_t* path2)
{
if (pal::is_path_rooted(path2))
{
path1.assign(path2);
path1->assign(path2);
}
else
{
if (path1.back() != DIR_SEPARATOR)
if (path1->empty() || path1->back() != DIR_SEPARATOR)
{
path1.push_back(DIR_SEPARATOR);
path1->push_back(DIR_SEPARATOR);
}
path1.append(path2);
path1->append(path2);
}
}
@ -70,3 +70,12 @@ pal::string_t get_directory(const pal::string_t& path)
return path.substr(0, path_sep);
}
void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl)
{
int pos = 0;
while ((pos = path->find(match, pos)) != pal::string_t::npos)
{
(*path)[pos] = repl;
}
}

View file

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

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 static void Main(string[] args)
public static int Main(string[] args)
{
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