From 02b6deb1243a000b32f1d4182484c3bbba0ef756 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 9 Dec 2015 11:50:48 -0600 Subject: [PATCH 01/21] Newline before "For OS X" Markdown does not render properly without it. --- Documentation/developer-guide.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/developer-guide.md b/Documentation/developer-guide.md index 58eb4e969..33860e360 100644 --- a/Documentation/developer-guide.md +++ b/Documentation/developer-guide.md @@ -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 @@ -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` \ No newline at end of file +- Update the `$Projects` property in `packaging/osx/scripts/postinstall` From 07eb7ef28f78b45e554e31454eb222ab9d543f41 Mon Sep 17 00:00:00 2001 From: Sridhar Periyasamy Date: Mon, 14 Dec 2015 17:39:29 -0800 Subject: [PATCH 02/21] Dotnet publish tests --- scripts/compile.ps1 | 15 +- scripts/compile.sh | 8 +- scripts/dotnet-restore.cmd | 2 +- scripts/test/e2e-test.ps1 | 44 -- scripts/test/runtests.ps1 | 68 ++++ scripts/test/{e2e-test.sh => runtests.sh} | 30 +- scripts/test/smoke-test.ps1 | 25 -- scripts/test/smoke-test.sh | 35 -- .../Microsoft.DotNet.Tools.Publish.Tests.cs | 176 ++++++++ ...Microsoft.DotNet.Tools.Publish.Tests.xproj | 19 + .../project.json | 24 ++ .../Assertions/CommandResultAssertions.cs | 72 ++++ .../Assertions/CommandResultExtensions.cs | 20 + .../Assertions/DirectoryInfoAssertions.cs | 67 ++++ .../Assertions/DirectoryInfoExtensions.cs | 19 + .../Commands/PublishCommand.cs | 107 +++++ .../Commands/RestoreCommand.cs | 23 ++ .../Commands/TestCommand.cs | 30 ++ ...icrosoft.DotNet.Tools.Test.Utilities.xproj | 20 + .../TempFileSystem/DisposableDirectory.cs | 30 ++ .../TempFileSystem/DisposableFile.cs | 83 ++++ .../TempFileSystem/FileNameUtilities.cs | 183 +++++++++ .../ImmutableArrayTestExtensions.cs | 43 ++ .../TempFileSystem/PathKind.cs | 43 ++ .../TempFileSystem/PathUtilities.cs | 379 ++++++++++++++++++ .../TempFileSystem/TempDirectory.cs | 94 +++++ .../TempFileSystem/TempFile.cs | 118 ++++++ .../TempFileSystem/TempRoot.cs | 72 ++++ .../TestBase.cs | 49 +++ .../project.json | 22 + .../CompileFail}/Program.cs | 0 .../CompileFail}/project.json | 0 test/{ => TestProjects}/TestApp/Program.cs | 3 +- test/{ => TestProjects}/TestApp/TestApp.xproj | 0 test/{ => TestProjects}/TestApp/project.json | 0 .../TestAppWithArgs/Program.cs | 0 .../TestAppWithArgs/project.json | 0 .../TestAppWithContents/Program.cs | 13 + .../TestAppWithContents/project.json | 18 + .../TestAppWithContents/testcontentfile.txt | 1 + test/{ => TestProjects}/TestLibrary/Helper.cs | 0 .../TestLibrary/TestLibrary.xproj | 0 .../TestLibrary/project.json | 0 43 files changed, 1826 insertions(+), 129 deletions(-) delete mode 100644 scripts/test/e2e-test.ps1 create mode 100644 scripts/test/runtests.ps1 rename scripts/test/{e2e-test.sh => runtests.sh} (54%) delete mode 100644 scripts/test/smoke-test.ps1 delete mode 100755 scripts/test/smoke-test.sh create mode 100644 test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs create mode 100644 test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.xproj create mode 100644 test/Microsoft.DotNet.Tools.Publish.Tests/project.json create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultExtensions.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoExtensions.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/RestoreCommand.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/Microsoft.DotNet.Tools.Test.Utilities.xproj create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableDirectory.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableFile.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/FileNameUtilities.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/ImmutableArrayTestExtensions.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathKind.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathUtilities.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempDirectory.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempFile.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempRoot.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.Utilities/project.json rename test/{compile/failing/SimpleCompilerError => TestProjects/CompileFail}/Program.cs (100%) rename test/{compile/failing/SimpleCompilerError => TestProjects/CompileFail}/project.json (100%) rename test/{ => TestProjects}/TestApp/Program.cs (83%) rename test/{ => TestProjects}/TestApp/TestApp.xproj (100%) rename test/{ => TestProjects}/TestApp/project.json (100%) rename test/{ => TestProjects}/TestAppWithArgs/Program.cs (100%) rename test/{ => TestProjects}/TestAppWithArgs/project.json (100%) create mode 100644 test/TestProjects/TestAppWithContents/Program.cs create mode 100644 test/TestProjects/TestAppWithContents/project.json create mode 100644 test/TestProjects/TestAppWithContents/testcontentfile.txt rename test/{ => TestProjects}/TestLibrary/Helper.cs (100%) rename test/{ => TestProjects}/TestLibrary/TestLibrary.xproj (100%) rename test/{ => TestProjects}/TestLibrary/project.json (100%) diff --git a/scripts/compile.ps1 b/scripts/compile.ps1 index a3e6e91ff..cd29cd760 100644 --- a/scripts/compile.ps1 +++ b/scripts/compile.ps1 @@ -130,20 +130,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 } diff --git a/scripts/compile.sh b/scripts/compile.sh index 3c7a05761..3f2557829 100755 --- a/scripts/compile.sh +++ b/scripts/compile.sh @@ -114,10 +114,6 @@ DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $REPOROOT/scripts/build/build_a 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 - # E2E test on the output -header "Testing stage2 End to End ..." -DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/e2e-test.sh +header "Testing stage2..." +DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $DIR/test/runtests.sh diff --git a/scripts/dotnet-restore.cmd b/scripts/dotnet-restore.cmd index c55412827..99bcf1eed 100644 --- a/scripts/dotnet-restore.cmd +++ b/scripts/dotnet-restore.cmd @@ -6,7 +6,7 @@ REM Licensed under the MIT license. See LICENSE file in the project root for ful SETLOCAL SET ERRORLEVEL= -"%~dp0dnx\dnx" "%~dp0dnx\Microsoft.Dnx.Tooling\Microsoft.Dnx.Tooling.dll" restore %* +"%~dp0dnx\dnx" "%~dp0dnx\lib\Microsoft.Dnx.Tooling\Microsoft.Dnx.Tooling.dll" restore %* exit /b %ERRORLEVEL% ENDLOCAL diff --git a/scripts/test/e2e-test.ps1 b/scripts/test/e2e-test.ps1 deleted file mode 100644 index 88c6ef99f..000000000 --- a/scripts/test/e2e-test.ps1 +++ /dev/null @@ -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 -} diff --git a/scripts/test/runtests.ps1 b/scripts/test/runtests.ps1 new file mode 100644 index 000000000..a7f9b05c8 --- /dev/null +++ b/scripts/test/runtests.ps1 @@ -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 diff --git a/scripts/test/e2e-test.sh b/scripts/test/runtests.sh similarity index 54% rename from scripts/test/e2e-test.sh rename to scripts/test/runtests.sh index 30fbc8743..c54c27673 100755 --- a/scripts/test/e2e-test.sh +++ b/scripts/test/runtests.sh @@ -16,12 +16,30 @@ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" source "$DIR/../_common.sh" -rm "$REPOROOT/test/E2E/project.lock.json" -dotnet restore --quiet "$REPOROOT/test/E2E" --runtime "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64" -dotnet publish --framework dnxcore50 --runtime "$RID" --output "$REPOROOT/artifacts/$RID/e2etest" "$REPOROOT/test/E2E" + +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 -R $REPOROOT/test/TestProjects/* $TestBinRoot/TestProjects # 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 +pushd "$TestBinRoot" + +for project in ${TestProjects[@]} +do + ./corerun "xunit.console.netcore.exe" "$project.dll" -xml "project.xml" +done + popd diff --git a/scripts/test/smoke-test.ps1 b/scripts/test/smoke-test.ps1 deleted file mode 100644 index 132751613..000000000 --- a/scripts/test/smoke-test.ps1 +++ /dev/null @@ -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 diff --git a/scripts/test/smoke-test.sh b/scripts/test/smoke-test.sh deleted file mode 100755 index 91f35b9d5..000000000 --- a/scripts/test/smoke-test.sh +++ /dev/null @@ -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 "osx.10.10-x64" --runtime "ubuntu.14.04-x64" --runtime "win7-x64" -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 diff --git a/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs b/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs new file mode 100644 index 000000000..bb6ae005f --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs @@ -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 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\"" }, + 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(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.xproj b/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.xproj new file mode 100644 index 000000000..93543daff --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 386d412c-003c-47b1-8258-0e35865cb7c4 + Microsoft.DotNet.Tools.Publish.Tests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Publish.Tests/project.json b/test/Microsoft.DotNet.Tools.Publish.Tests/project.json new file mode 100644 index 000000000..8eb748a1b --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Publish.Tests/project.json @@ -0,0 +1,24 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1-*", + "Microsoft.NETCore.Runtime": "1.0.1-*", + "Microsoft.NETCore.TestHost" : "1.0.0-*", + "System.Console": "4.0.0-beta-*", + "System.Runtime": "4.0.21-beta-*", + "System.AppContext": "4.0.1-beta-*", + + "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": { } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs new file mode 100644 index 000000000..aa2508146 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs @@ -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 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(this); + } + + public AndConstraint Pass() + { + Execute.Assertion.ForCondition(_commandResult.ExitCode == 0) + .FailWith("Expected command to pass but it exited with {0}.", _commandResult.ExitCode); + return new AndConstraint(this); + } + + public AndConstraint Fail() + { + Execute.Assertion.ForCondition(_commandResult.ExitCode != 0) + .FailWith("Expected command to fail but it exited with {0}.", _commandResult.ExitCode); + return new AndConstraint(this); + } + + public AndConstraint HaveStdOut() + { + Execute.Assertion.ForCondition(!string.IsNullOrEmpty(_commandResult.StdOut)) + .FailWith("Command did not output anything to stdout"); + return new AndConstraint(this); + } + + public AndConstraint HaveStdErr() + { + Execute.Assertion.ForCondition(!string.IsNullOrEmpty(_commandResult.StdErr)) + .FailWith("Command did not output anything to stderr"); + return new AndConstraint(this); + } + + public AndConstraint 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(this); + } + + public AndConstraint 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(this); + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultExtensions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultExtensions.cs new file mode 100644 index 000000000..d29ab52b2 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultExtensions.cs @@ -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); + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs new file mode 100644 index 000000000..efb54d6e7 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs @@ -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 Exist() + { + _dirInfo.Exists.Should().BeTrue(); + Execute.Assertion.ForCondition(_dirInfo.Exists) + .FailWith("Expected directory {0} does not exist.", _dirInfo.FullName); + return new AndConstraint(this); + } + + public AndConstraint 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(this); + } + + public AndConstraint 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(this); + } + + public AndConstraint HaveFiles(IEnumerable expectedFiles) + { + foreach (var expectedFile in expectedFiles) + { + HaveFile(expectedFile); + } + + return new AndConstraint(this); + } + + public AndConstraint 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(new DirectoryInfoAssertions(dir)); + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoExtensions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoExtensions.cs new file mode 100644 index 000000000..6df3942ee --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoExtensions.cs @@ -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); + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs new file mode 100644 index 000000000..90cdd5310 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs @@ -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}"; + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/RestoreCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/RestoreCommand.cs new file mode 100644 index 000000000..00aa96d09 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/RestoreCommand.cs @@ -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); + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs new file mode 100644 index 000000000..c0b3a9cc4 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs @@ -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; + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Microsoft.DotNet.Tools.Test.Utilities.xproj b/test/Microsoft.DotNet.Tools.Tests.Utilities/Microsoft.DotNet.Tools.Test.Utilities.xproj new file mode 100644 index 000000000..a7bd37675 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Microsoft.DotNet.Tools.Test.Utilities.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + e4f46eab-b5a5-4e60-9b9d-40a1fadbf45c + Microsoft.DotNet.Tools.Test.Utilities + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableDirectory.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableDirectory.cs new file mode 100644 index 000000000..8baa5a15e --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableDirectory.cs @@ -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 + { + } + } + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableFile.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableFile.cs new file mode 100644 index 000000000..c17c1dd9b --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/DisposableFile.cs @@ -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)); + } + + /// + /// 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. + /// + 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); + } + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/FileNameUtilities.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/FileNameUtilities.cs new file mode 100644 index 000000000..625370ba1 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/FileNameUtilities.cs @@ -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 +{ + /// + /// 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. + /// + internal static class FileNameUtilities + { + private const string DirectorySeparatorStr = "\\"; + internal const char DirectorySeparatorChar = '\\'; + internal const char AltDirectorySeparatorChar = '/'; + internal const char VolumeSeparatorChar = ':'; + + /// + /// Returns true if the string represents an unqualified file name. + /// The name may contain any characters but directory and volume separators. + /// + /// Path. + /// + /// True if is a simple file name, false if it is null or includes a directory specification. + /// + internal static bool IsFileName(string path) + { + return IndexOfFileName(path) == 0; + } + + /// + /// Returns the offset in where the dot that starts an extension is, or -1 if the path doesn't have an extension. + /// + /// + /// Returns 0 for path ".foo". + /// Returns -1 for path "foo.". + /// + 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; + } + + /// + /// Returns an extension of the specified path string. + /// + /// + /// The same functionality as but doesn't throw an exception + /// if there are invalid characters in the path. + /// + internal static string GetExtension(string path) + { + if (path == null) + { + return null; + } + + int index = IndexOfExtension(path); + return (index >= 0) ? path.Substring(index) : string.Empty; + } + + /// + /// Removes extension from path. + /// + /// + /// Returns "foo" for path "foo.". + /// Returns "foo.." for path "foo...". + /// + 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; + } + + /// + /// Returns path with the extension changed to . + /// + /// + /// Equivalent of + /// + /// If 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 . + /// + 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; + } + + /// + /// Returns the position in given path where the file name starts. + /// + /// -1 if path is null. + 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; + } + + /// + /// Get file name from path. + /// + /// Unlike doesn't check for invalid path characters. + internal static string GetFileName(string path) + { + int fileNameStart = IndexOfFileName(path); + return (fileNameStart <= 0) ? path : path.Substring(fileNameStart); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/ImmutableArrayTestExtensions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/ImmutableArrayTestExtensions.cs new file mode 100644 index 000000000..b3dcb57bb --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/ImmutableArrayTestExtensions.cs @@ -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 +{ + /// + /// The collection of extension methods for the type + /// + public static class ImmutableArrayTestExtensions + { + /// + /// Writes read-only array of bytes to the specified file. + /// + /// Data to write to the file. + /// File path. + internal static void WriteToFile(this ImmutableArray 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 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; + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathKind.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathKind.cs new file mode 100644 index 000000000..0c37a6ffb --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathKind.cs @@ -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 + { + /// + /// Null or empty. + /// + Empty, + + /// + /// "file" + /// + Relative, + + /// + /// ".\file" + /// + RelativeToCurrentDirectory, + + /// + /// "..\file" + /// + RelativeToCurrentParent, + + /// + /// "\dir\file" + /// + RelativeToCurrentRoot, + + /// + /// "C:dir\file" + /// + RelativeToDriveDirectory, + + /// + /// "C:\file" or "\\machine" (UNC). + /// + Absolute, + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathUtilities.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathUtilities.cs new file mode 100644 index 000000000..9b1b7b8e3 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/PathUtilities.cs @@ -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); + } + + /// + /// Get directory name from path. + /// + /// + /// Unlike 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). + /// + /// Prefix of path that represents a directory. + 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]); + } + + /// + /// Returns true if given path is absolute and starts with a drive specification ("C:\"). + /// + private static bool IsDriveRootedAbsolutePath(string path) + { + Debug.Assert(!IsUnixLikePlatform); + return path.Length >= 3 && path[1] == VolumeSeparatorChar && IsDirectorySeparator(path[2]); + } + + /// + /// Get a prefix of given path which is the root of the path. + /// + /// + /// 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.) . + /// + 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; + } + + /// + /// Calculates the length of root of an UNC path. + /// + /// + /// "\\server\share" is root of UNC path "\\server\share\dir1\dir2\file". + /// + 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; + } + + /// + /// Combines an absolute path with a relative. + /// + /// Absolute root path. + /// Relative path. + /// + /// An absolute combined path, or null if 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"). + /// + /// + internal static string CombineAbsoluteAndRelativePaths(string root, string relativePath) + { + Debug.Assert(IsAbsolute(root)); + + return CombinePossiblyRelativeAndRelativePaths(root, relativePath); + } + + /// + /// Combine two paths, the first of which may be absolute. + /// + /// First path: absolute, relative, or null. + /// Second path: relative and non-null. + /// null, if is null; a combined path, otherwise. + /// + 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; + } + } + + /// + /// Determines whether an assembly reference is considered an assembly file path or an assembly name. + /// used, for example, on values of /r and #r. + /// + 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; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempDirectory.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempDirectory.cs new file mode 100644 index 000000000..afca09075 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempDirectory.cs @@ -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; } + } + + /// + /// Creates a file in this directory. + /// + /// File name. + public TempFile CreateFile(string name) + { + string filePath = System.IO.Path.Combine(_path, name); + TempRoot.CreateStream(filePath); + return _root.AddFile(new DisposableFile(filePath)); + } + + /// + /// Creates a file in this directory that is a copy of the specified file. + /// + 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)); + } + + /// + /// Creates a subdirectory in this directory. + /// + /// Directory name or unrooted directory path. + 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; + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempFile.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempFile.cs new file mode 100644 index 000000000..541987880 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempFile.cs @@ -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 WriteAllTextAsync(string content, Encoding encoding) + { + using (var sw = new StreamWriter(File.Create(_path), encoding)) + { + await sw.WriteAsync(content).ConfigureAwait(false); + } + + return this; + } + + public Task WriteAllTextAsync(string content) + { + return WriteAllTextAsync(content, Encoding.UTF8); + } + + public TempFile WriteAllBytes(byte[] content) + { + File.WriteAllBytes(_path, content); + return this; + } + + public TempFile WriteAllBytes(ImmutableArray 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; + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempRoot.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempRoot.cs new file mode 100644 index 000000000..28b4269c5 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TempFileSystem/TempRoot.cs @@ -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 _temps = new List(); + 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 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)) { } + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs new file mode 100644 index 000000000..e7f7ae798 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs @@ -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 +{ + + /// + /// Base class for all unit test classes. + /// + 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(); + } + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json b/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json new file mode 100644 index 000000000..1ddf80521 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json @@ -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": { } + } +} diff --git a/test/compile/failing/SimpleCompilerError/Program.cs b/test/TestProjects/CompileFail/Program.cs similarity index 100% rename from test/compile/failing/SimpleCompilerError/Program.cs rename to test/TestProjects/CompileFail/Program.cs diff --git a/test/compile/failing/SimpleCompilerError/project.json b/test/TestProjects/CompileFail/project.json similarity index 100% rename from test/compile/failing/SimpleCompilerError/project.json rename to test/TestProjects/CompileFail/project.json diff --git a/test/TestApp/Program.cs b/test/TestProjects/TestApp/Program.cs similarity index 83% rename from test/TestApp/Program.cs rename to test/TestProjects/TestApp/Program.cs index b403a4337..ac3163a58 100644 --- a/test/TestApp/Program.cs +++ b/test/TestProjects/TestApp/Program.cs @@ -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; } } } diff --git a/test/TestApp/TestApp.xproj b/test/TestProjects/TestApp/TestApp.xproj similarity index 100% rename from test/TestApp/TestApp.xproj rename to test/TestProjects/TestApp/TestApp.xproj diff --git a/test/TestApp/project.json b/test/TestProjects/TestApp/project.json similarity index 100% rename from test/TestApp/project.json rename to test/TestProjects/TestApp/project.json diff --git a/test/TestAppWithArgs/Program.cs b/test/TestProjects/TestAppWithArgs/Program.cs similarity index 100% rename from test/TestAppWithArgs/Program.cs rename to test/TestProjects/TestAppWithArgs/Program.cs diff --git a/test/TestAppWithArgs/project.json b/test/TestProjects/TestAppWithArgs/project.json similarity index 100% rename from test/TestAppWithArgs/project.json rename to test/TestProjects/TestAppWithArgs/project.json diff --git a/test/TestProjects/TestAppWithContents/Program.cs b/test/TestProjects/TestAppWithContents/Program.cs new file mode 100644 index 000000000..28e184cb5 --- /dev/null +++ b/test/TestProjects/TestAppWithContents/Program.cs @@ -0,0 +1,13 @@ +using System; + +namespace ConsoleApplication +{ + public class Program + { + public static int Main(string[] args) + { + Console.WriteLine("Hello World!"); + return 100; + } + } +} diff --git a/test/TestProjects/TestAppWithContents/project.json b/test/TestProjects/TestAppWithContents/project.json new file mode 100644 index 000000000..396f90231 --- /dev/null +++ b/test/TestProjects/TestAppWithContents/project.json @@ -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": { } + } +} diff --git a/test/TestProjects/TestAppWithContents/testcontentfile.txt b/test/TestProjects/TestAppWithContents/testcontentfile.txt new file mode 100644 index 000000000..9a1cc0232 --- /dev/null +++ b/test/TestProjects/TestAppWithContents/testcontentfile.txt @@ -0,0 +1 @@ +This is to test if contents are copied correctly by the dotnet tools \ No newline at end of file diff --git a/test/TestLibrary/Helper.cs b/test/TestProjects/TestLibrary/Helper.cs similarity index 100% rename from test/TestLibrary/Helper.cs rename to test/TestProjects/TestLibrary/Helper.cs diff --git a/test/TestLibrary/TestLibrary.xproj b/test/TestProjects/TestLibrary/TestLibrary.xproj similarity index 100% rename from test/TestLibrary/TestLibrary.xproj rename to test/TestProjects/TestLibrary/TestLibrary.xproj diff --git a/test/TestLibrary/project.json b/test/TestProjects/TestLibrary/project.json similarity index 100% rename from test/TestLibrary/project.json rename to test/TestProjects/TestLibrary/project.json From f7ae98eb1dcadf07139b6b4a7e678f675e901e7d Mon Sep 17 00:00:00 2001 From: Sridhar Periyasamy Date: Tue, 15 Dec 2015 09:46:21 -0800 Subject: [PATCH 03/21] Fix runtests.sh --- scripts/test/runtests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test/runtests.sh b/scripts/test/runtests.sh index c54c27673..a31263201 100755 --- a/scripts/test/runtests.sh +++ b/scripts/test/runtests.sh @@ -39,7 +39,7 @@ pushd "$TestBinRoot" for project in ${TestProjects[@]} do - ./corerun "xunit.console.netcore.exe" "$project.dll" -xml "project.xml" + ./corerun "xunit.console.netcore.exe" "$project.dll" -xml "project.xml" -notrait category=failing done popd From 771b0b1eb14acf2f14e4832fd021380784ca3627 Mon Sep 17 00:00:00 2001 From: Sridhar Periyasamy Date: Tue, 15 Dec 2015 10:04:16 -0800 Subject: [PATCH 04/21] Fix build break --- src/Microsoft.DotNet.Cli.Utils/CommandResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResult.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResult.cs index f5a38f9e9..c49c40c00 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResult.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResult.cs @@ -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(); From 6046de8efe15bf41b7eaeab9230a4abc66d037a1 Mon Sep 17 00:00:00 2001 From: Sridhar Periyasamy Date: Wed, 16 Dec 2015 13:04:29 -0800 Subject: [PATCH 05/21] Address PR feedback. --- scripts/test/runtests.sh | 21 +++++++++++++++++-- .../Microsoft.DotNet.Tools.Publish.Tests.cs | 2 +- .../project.json | 6 +----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/scripts/test/runtests.sh b/scripts/test/runtests.sh index a31263201..7a8726a88 100755 --- a/scripts/test/runtests.sh +++ b/scripts/test/runtests.sh @@ -32,14 +32,31 @@ done # copy TestProjects folder which is used by the test cases mkdir -p "$TestBinRoot/TestProjects" -cp -R $REPOROOT/test/TestProjects/* $TestBinRoot/TestProjects +cp -a $REPOROOT/test/TestProjects/* $TestBinRoot/TestProjects + -# set -e will abort if the exit code of this is non-zero 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 diff --git a/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs b/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs index bb6ae005f..22e8d5d25 100644 --- a/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs +++ b/test/Microsoft.DotNet.Tools.Publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs @@ -27,7 +27,7 @@ namespace Microsoft.DotNet.Tools.Publish.Tests new object[] { "", RuntimeIdentifier.Current, "", "" }, new object[] { "", "", "Release", "" }, new object[] { "", "", "", "some/dir"}, - //new object[] { "", "", "", "\"some/dir/with spaces\"" }, + //new object[] { "", "", "", "\"some/dir/with spaces\"" }, // issue - https://github.com/dotnet/cli/issues/525 new object[] { "dnxcore50", RuntimeIdentifier.Current, "Debug", "some/dir" }, }; } diff --git a/test/Microsoft.DotNet.Tools.Publish.Tests/project.json b/test/Microsoft.DotNet.Tools.Publish.Tests/project.json index 8eb748a1b..9b0bb1546 100644 --- a/test/Microsoft.DotNet.Tools.Publish.Tests/project.json +++ b/test/Microsoft.DotNet.Tools.Publish.Tests/project.json @@ -2,12 +2,8 @@ "version": "1.0.0-*", "dependencies": { - "Microsoft.NETCore.Platforms": "1.0.1-*", - "Microsoft.NETCore.Runtime": "1.0.1-*", + "NETStandard.Library": "1.0.0-rc2-23614", "Microsoft.NETCore.TestHost" : "1.0.0-*", - "System.Console": "4.0.0-beta-*", - "System.Runtime": "4.0.21-beta-*", - "System.AppContext": "4.0.1-beta-*", "xunit": "2.1.0", "xunit.console.netcore": "1.0.2-prerelease-00101", From af82cf7940e7aaf9b8e2bc673dbad7d6f13053b5 Mon Sep 17 00:00:00 2001 From: Enrico Sada Date: Thu, 17 Dec 2015 15:16:29 +0100 Subject: [PATCH 06/21] typo --- Documentation/developer-guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/developer-guide.md b/Documentation/developer-guide.md index 58eb4e969..a83a7aeb9 100644 --- a/Documentation/developer-guide.md +++ b/Documentation/developer-guide.md @@ -37,7 +37,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 +63,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` \ No newline at end of file +- Update the `$Projects` property in `packaging/osx/scripts/postinstall` From 7c722b3fe4830bb970fcde4fdca5e29edf90967e Mon Sep 17 00:00:00 2001 From: piotrp Date: Thu, 17 Dec 2015 17:05:05 -0800 Subject: [PATCH 07/21] Offline builds on Windows --- scripts/build.ps1 | 9 ++++-- scripts/compile.ps1 | 69 ++++++++++++++++++++++++--------------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 2c6e6fac6..024f2d3df 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -4,7 +4,8 @@ # param( - [string]$Configuration="Debug") + [string]$Configuration="Debug", + [string]$Offline=$false) . "$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 diff --git a/scripts/compile.ps1 b/scripts/compile.ps1 index 9ec813ac6..24b5d02fc 100644 --- a/scripts/compile.ps1 +++ b/scripts/compile.ps1 @@ -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", + [string]$Offline = $false) $ErrorActionPreference="Stop" @@ -23,40 +24,44 @@ 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" + } + + # 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 + } } - # 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 { From 344d0106104f2627af2029f3bd4bec0d2fd56649 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Fri, 18 Dec 2015 01:39:50 +0000 Subject: [PATCH 08/21] Linux Port of offline build --- build.sh | 10 +++++++++- scripts/compile.sh | 42 +++++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/build.sh b/build.sh index e6f525939..35670e35c 100755 --- a/build.sh +++ b/build.sh @@ -4,7 +4,8 @@ # Licensed under the MIT license. See LICENSE file in the project root for full license information. # -# $1 is passed to package to enable deb or pkg packaging +# Set OFFLINE environment variable to build offline + set -e SOURCE="${BASH_SOURCE[0]}" @@ -27,12 +28,19 @@ do debug) export CONFIGURATION=Debug ;; + offline) + export OFFLINE=true + ;; *) esac done [ -z "$CONFIGURATION" ] && CONFIGURATION=Debug +if [ ! -z "$OFFLINE" ]; then + header " - Offline Build - " +fi + # Use a repo-local install directory (but not the artifacts directory because that gets cleaned a lot export DOTNET_INSTALL_DIR=$DIR/.dotnet_stage0/$RID [ -d $DOTNET_INSTALL_DIR ] || mkdir -p $DOTNET_INSTALL_DIR diff --git a/scripts/compile.sh b/scripts/compile.sh index 1c2f0f88b..705a16a2b 100755 --- a/scripts/compile.sh +++ b/scripts/compile.sh @@ -25,30 +25,34 @@ fi [ -z "$CONFIGURATION" ] && export CONFIGURATION=Debug -# Download DNX to copy into stage2 -header "Downloading DNX $DNX_VERSION" -DNX_URL="https://api.nuget.org/packages/$DNX_FLAVOR.$DNX_VERSION.nupkg" -DNX_ROOT="$DNX_DIR/bin" -rm -rf $DNX_DIR -mkdir -p $DNX_DIR -curl -o $DNX_DIR/dnx.zip $DNX_URL --silent -unzip -qq $DNX_DIR/dnx.zip -d $DNX_DIR -chmod a+x $DNX_ROOT/dnu $DNX_ROOT/dnx +if [[ ! -z "$OFFLINE" ]]; then + header "Skipping Stage 0, Dnx, and Packages download: Offline Build" +else + # Download DNX to copy into stage2 + header "Downloading DNX $DNX_VERSION" + DNX_URL="https://api.nuget.org/packages/$DNX_FLAVOR.$DNX_VERSION.nupkg" + DNX_ROOT="$DNX_DIR/bin" + rm -rf $DNX_DIR + mkdir -p $DNX_DIR + curl -o $DNX_DIR/dnx.zip $DNX_URL --silent + unzip -qq $DNX_DIR/dnx.zip -d $DNX_DIR + chmod a+x $DNX_ROOT/dnu $DNX_ROOT/dnx -# 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" From 9d8f5295904c9fdf8d221217a92fe8a616b2a5f5 Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Fri, 18 Dec 2015 10:30:04 -0800 Subject: [PATCH 09/21] Handling help and error manually to prevent a call to Environment.FailFast call in System.CommandLine --- src/Microsoft.DotNet.Tools.Run/Program.cs | 46 ++++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.DotNet.Tools.Run/Program.cs b/src/Microsoft.DotNet.Tools.Run/Program.cs index 6bedd40dd..e8f3ff7bc 100644 --- a/src/Microsoft.DotNet.Tools.Run/Program.cs +++ b/src/Microsoft.DotNet.Tools.Run/Program.cs @@ -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 { From e8a7228feb905746c9fd13080173304e1c51dc83 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Fri, 18 Dec 2015 11:34:55 -0800 Subject: [PATCH 10/21] Dotnet pack test Enable Dotnet Pack Test.. Not sure why this method didn't have a fact attribute. Thanks for pointing this out @krwq --- test/E2E/E2ETest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/E2E/E2ETest.cs b/test/E2E/E2ETest.cs index 13788bdbc..d35e8698a 100644 --- a/test/E2E/E2ETest.cs +++ b/test/E2E/E2ETest.cs @@ -90,7 +90,8 @@ namespace ConsoleApplication TestRunCommand("dotnet", $"run"); } - + + [Fact] public void TestDotnetPack() { TestSetup(); From f05b208ad7b1d4d3c855a0213b1f431ee07cf068 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 17 Dec 2015 15:04:18 -0800 Subject: [PATCH 11/21] Add dependency model api --- Microsoft.DotNet.Cli.sln | 19 +++ .../CommonCompilerOptions.cs | 7 + .../DependencyContextBuilder.cs | 53 ++++++++ .../ProjectReader.cs | 3 +- .../project.json | 4 + .../Program.cs | 43 +++++- .../project.json | 4 + .../Dependency.cs | 17 +++ .../DependencyContext.cs | 52 +++++++ .../DependencyContextReader.cs | 127 ++++++++++++++++++ .../DependencyContextStrings.cs | 23 ++++ .../DependencyContextWriter.cs | 86 ++++++++++++ .../Library.cs | 36 +++++ ...Microsoft.Extensions.DependencyModel.xproj | 3 +- .../project.json | 29 ++++ 15 files changed, 499 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/Dependency.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/DependencyContext.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/DependencyContextReader.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/Library.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/project.json diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index ca2cf369b..3459dd71c 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -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 diff --git a/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs b/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs index ef97c3b4d..2f1555b00 100644 --- a/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs +++ b/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs @@ -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; diff --git a/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs b/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs new file mode 100644 index 000000000..42d63f164 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs @@ -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 dependencies, Func> assemblySelector) + { + return dependencies.Select(export => GetLibrary(export, assemblySelector(export), dependencies)).ToArray(); + } + + private static Library GetLibrary(LibraryExport export, IEnumerable libraryAssets, IEnumerable 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 dependencies) + { + var version = + dependencies.First(d => d.Library.Identity.Name == libraryRange.Name) + .Library.Identity.Version.ToString(); + return new Dependency(libraryRange.Name, version); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs index 4e76c4215..d9b0915a1 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs @@ -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") }; } diff --git a/src/Microsoft.DotNet.ProjectModel/project.json b/src/Microsoft.DotNet.ProjectModel/project.json index 6baf9df20..b9baad7e4 100644 --- a/src/Microsoft.DotNet.ProjectModel/project.json +++ b/src/Microsoft.DotNet.ProjectModel/project.json @@ -19,6 +19,10 @@ "Microsoft.Extensions.HashCodeCombiner.Sources": { "type": "build", "version": "1.0.0-*" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "build", + "version": "1.0.0-*" } }, diff --git a/src/Microsoft.DotNet.Tools.Compiler/Program.cs b/src/Microsoft.DotNet.Tools.Compiler/Program.cs index d48c16b8e..fa968995a 100644 --- a/src/Microsoft.DotNet.Tools.Compiler/Program.cs +++ b/src/Microsoft.DotNet.Tools.Compiler/Program.cs @@ -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 { @@ -304,6 +305,8 @@ namespace Microsoft.DotNet.Tools.Compiler compilationOptions.KeyFile = Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile)); } + var references = new List(); + // Add compilation options to the args compilerArgs.AddRange(compilationOptions.SerializeToArgs()); @@ -319,16 +322,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 +430,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); diff --git a/src/Microsoft.DotNet.Tools.Compiler/project.json b/src/Microsoft.DotNet.Tools.Compiler/project.json index 2c34cffbb..bd672d18d 100644 --- a/src/Microsoft.DotNet.Tools.Compiler/project.json +++ b/src/Microsoft.DotNet.Tools.Compiler/project.json @@ -16,6 +16,10 @@ "Microsoft.Extensions.CommandLineUtils.Sources": { "type": "build", "version": "1.0.0-*" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "build", + "version": "1.0.0-*" } }, "frameworks": { diff --git a/src/Microsoft.Extensions.DependencyModel/Dependency.cs b/src/Microsoft.Extensions.DependencyModel/Dependency.cs new file mode 100644 index 000000000..7950220af --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/Dependency.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContext.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContext.cs new file mode 100644 index 000000000..69f112e4c --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContext.cs @@ -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 CompileLibraries { get; } + + public IReadOnlyList 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); + } + } +} diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextReader.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextReader.cs new file mode 100644 index 000000000..f181298b2 --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextReader.cs @@ -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>) 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 libraryStubs) + { + return librariesObject.Properties().Select(property => ReadLibrary(property, runtime, libraryStubs)).ToArray(); + } + + private Library ReadLibrary(JProperty property, bool runtime, Dictionary 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 ReadLibraryStubs(JObject librariesObject) + { + var libraries = new Dictionary(); + foreach (var libraryProperty in librariesObject) + { + var value = (JObject) libraryProperty.Value; + var stub = new LibraryStub + { + Name = libraryProperty.Key, + Hash = value[DependencyContextStrings.Sha512PropertyName]?.Value(), + Type = value[DependencyContextStrings.TypePropertyName].Value(), + Serviceable = value[DependencyContextStrings.ServiceablePropertyName]?.Value() == true + }; + libraries.Add(stub.Name, stub); + } + return libraries; + } + + private struct LibraryStub + { + public string Name; + + public string Hash; + + public string Type; + + public bool Serviceable; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs new file mode 100644 index 000000000..9b8f81429 --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs @@ -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"; + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs new file mode 100644 index 000000000..1b3a1aa68 --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs @@ -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 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 assemblies) + { + return new JObject(assemblies.Select(assembly => new JProperty(assembly, new JObject()))); + } + + private JObject WriteDependencies(IReadOnlyList 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) + ); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/Library.cs b/src/Microsoft.Extensions.DependencyModel/Library.cs new file mode 100644 index 000000000..3f37a69ec --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/Library.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using 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 Assemblies { get; } + + public IReadOnlyList Dependencies { get; } + + public bool Serviceable { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/Microsoft.Extensions.DependencyModel.xproj b/src/Microsoft.Extensions.DependencyModel/Microsoft.Extensions.DependencyModel.xproj index 2a8c89ff4..8f65b6a1b 100644 --- a/src/Microsoft.Extensions.DependencyModel/Microsoft.Extensions.DependencyModel.xproj +++ b/src/Microsoft.Extensions.DependencyModel/Microsoft.Extensions.DependencyModel.xproj @@ -6,12 +6,11 @@ - 482b1045-a1fa-4063-a0d9-a8107a91a016 + 688870c8-9843-4f9e-8576-d39290ad0f25 Microsoft.Extensions.DependencyModel ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\bin\$(MSBuildProjectName)\ - 2.0 diff --git a/src/Microsoft.Extensions.DependencyModel/project.json b/src/Microsoft.Extensions.DependencyModel/project.json new file mode 100644 index 000000000..1d2308e67 --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/project.json @@ -0,0 +1,29 @@ +{ + "description": "Abstractions for reading `.deps` files.", + "version": "1.0.0-*", + "repository": { + "type": "git", + "url": "git://github.com/dotnet/cli" + }, + "compilationOptions": { + "warningsAsErrors": true + }, + "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\"" + ] + } +} \ No newline at end of file From 4c0458233d600a02992fa3f7e6d264732de86a38 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Fri, 18 Dec 2015 12:27:17 -0800 Subject: [PATCH 12/21] add nuget.config to dotnet-new template --- src/Microsoft.DotNet.Tools.New/Program.cs | 4 +++- src/Microsoft.DotNet.Tools.New/Template/NuGet.Config | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.DotNet.Tools.New/Template/NuGet.Config diff --git a/src/Microsoft.DotNet.Tools.New/Program.cs b/src/Microsoft.DotNet.Tools.New/Program.cs index ca77d19b6..a6588c164 100644 --- a/src/Microsoft.DotNet.Tools.New/Program.cs +++ b/src/Microsoft.DotNet.Tools.New/Program.cs @@ -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(); diff --git a/src/Microsoft.DotNet.Tools.New/Template/NuGet.Config b/src/Microsoft.DotNet.Tools.New/Template/NuGet.Config new file mode 100644 index 000000000..ac9daec7a --- /dev/null +++ b/src/Microsoft.DotNet.Tools.New/Template/NuGet.Config @@ -0,0 +1,9 @@ + + + + + + + + + From 0bcc899ce355e2a2df16c9d41deae61777920daa Mon Sep 17 00:00:00 2001 From: piotrp Date: Fri, 18 Dec 2015 12:48:37 -0800 Subject: [PATCH 13/21] Make default Online --- scripts/build.ps1 | 2 +- scripts/compile.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 024f2d3df..39e1a46da 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -5,7 +5,7 @@ param( [string]$Configuration="Debug", - [string]$Offline=$false) + [switch]$Offline) . "$PSScriptRoot\_common.ps1" diff --git a/scripts/compile.ps1 b/scripts/compile.ps1 index 24b5d02fc..7fb6f586a 100644 --- a/scripts/compile.ps1 +++ b/scripts/compile.ps1 @@ -4,7 +4,7 @@ # param([string]$Configuration = "Debug", - [string]$Offline = $false) + [switch]$Offline) $ErrorActionPreference="Stop" From 3a2b87a92d8d866503469b5af3859628158b6616 Mon Sep 17 00:00:00 2001 From: Sridhar Periyasamy Date: Fri, 18 Dec 2015 17:04:29 -0800 Subject: [PATCH 14/21] Fix to not download Dnx always during a build. --- scripts/_common.ps1 | 3 --- scripts/compile.ps1 | 45 +++++++++++++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/scripts/_common.ps1 b/scripts/_common.ps1 index 968c36e01..adc30cbdd 100644 --- a/scripts/_common.ps1 +++ b/scripts/_common.ps1 @@ -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" diff --git a/scripts/compile.ps1 b/scripts/compile.ps1 index 8da28f382..c932b8798 100644 --- a/scripts/compile.ps1 +++ b/scripts/compile.ps1 @@ -14,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 @@ -42,22 +72,13 @@ Download it from https://www.cmake.org $DotNetTools = "$($env:LOCALAPPDATA)\Microsoft\dotnet" } - # 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" + $DnxRoot = getDnx # 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 + & "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --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 + Write-Host "Command failed: " "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --no-cache Exit 1 } } From 57cbc119de7b6625df9a3dc9dda1b2fa06a164a1 Mon Sep 17 00:00:00 2001 From: Gaurav Khanna Date: Sat, 19 Dec 2015 12:14:46 -0800 Subject: [PATCH 15/21] Add support --ilcsdkpath to allow ILCompiler to consume a custom build of ILCompiler.SDK --- .../ArgValues.cs | 6 ++++++ .../ArgumentsParser.cs | 6 +++++- .../ILCompilerInvoker.cs | 5 ++--- .../Linux/LinuxCppCompileStep.cs | 8 +++---- .../Linux/LinuxRyuJitCompileStep.cs | 8 +++---- .../Mac/MacCppCompileStep.cs | 8 +++---- .../Mac/MacRyuJitCompileStep.cs | 8 +++---- .../Windows/WindowsLinkStep.cs | 8 +++---- .../NativeCompileSettings.cs | 21 +++++++++++++++++++ .../Program.cs | 16 +++++++++++--- 10 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs index 27f4ad8c8..913eb42d6 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs @@ -16,6 +16,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native public IEnumerable LinkLibPaths { get; set; } public string AppDepSDKPath { get; set; } public string IlcPath { get; set; } + public string IlcSdkPath { get; set; } public bool IsHelp { get; set; } public int ReturnCode { get; set; } @@ -57,6 +58,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Native config.IlcPath = IlcPath; } + if (!string.IsNullOrEmpty(IlcSdkPath)) + { + config.IlcSdkPath = IlcSdkPath; + } + if (!string.IsNullOrEmpty(LogPath)) { config.LogPath = LogPath; diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs index 3a81ecdcc..f7d65588d 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs @@ -17,6 +17,7 @@ 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; @@ -45,7 +46,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 @@ -125,6 +128,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native ReferencePaths = references, IlcArgs = ilcArgs, IlcPath = ilcPath, + IlcSdkPath = ilcSdkPath, LinkLibPaths = linklib, AppDepSDKPath = appDepSdk, LogPath = logPath diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/ILCompilerInvoker.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/ILCompilerInvoker.cs index 7f862181d..9be41ac66 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/ILCompilerInvoker.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/ILCompilerInvoker.cs @@ -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}\""); diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs index 4ebd39b87..264ef6bc8 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs @@ -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", @@ -79,10 +79,10 @@ namespace Microsoft.DotNet.Tools.Compiler.Native // Add Stubs argsList.Add(Path.Combine(config.AppDepSDKPath, "CPPSdk/ubuntu.14.04/lxstubs.cpp")); - // 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(libPath); } diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs index 73253aea0..74855c80b 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs @@ -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,10 @@ namespace Microsoft.DotNet.Tools.Compiler.Native var inLibFile = DetermineInFile(config); argsList.Add(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(libPath); } diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs index cd35f5b68..a60a27d76 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs @@ -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", @@ -84,10 +84,10 @@ namespace Microsoft.DotNet.Tools.Compiler.Native // Lib flags argsList.Add(libFlags); - // 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); // Forward the library to linked to the linker argsList.Add("-Xlinker"); diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs index 6b70ac06f..7863e2b3f 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs @@ -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", @@ -75,10 +75,10 @@ namespace Microsoft.DotNet.Tools.Compiler.Native 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); } diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs index 446d2a52c..ef3e03f92 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs @@ -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 ModeLibMap = new Dictionary + private static readonly Dictionary IlcSdkLibMap = new Dictionary { { NativeIntermediateMode.cpp, new string[] { "PortableRuntime.lib", "bootstrappercpp.lib" } }, { NativeIntermediateMode.ryujit, new string[] { "Runtime.lib", "bootstrapper.lib" } } @@ -98,11 +98,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}\""); } diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs index 857f81f65..268d5cc36 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs @@ -14,6 +14,7 @@ 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; @@ -129,12 +130,32 @@ 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; + } + } + private NativeCompileSettings() { _linkLibPaths = new List(); _referencePaths = new List(); 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; diff --git a/src/Microsoft.DotNet.Tools.Compiler/Program.cs b/src/Microsoft.DotNet.Tools.Compiler/Program.cs index fa968995a..366f4765f 100644 --- a/src/Microsoft.DotNet.Tools.Compiler/Program.cs +++ b/src/Microsoft.DotNet.Tools.Compiler/Program.cs @@ -45,7 +45,8 @@ namespace Microsoft.DotNet.Tools.Compiler var arch = app.Option("-a|--arch ", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue); var ilcArgs = app.Option("--ilcargs ", "Command line arguments to be passed directly to ILCompiler.", CommandOptionType.SingleValue); var ilcPath = app.Option("--ilcpath ", "Path to the folder containing custom built ILCompiler.", CommandOptionType.SingleValue); - var ilcSdkPath = app.Option("--ilcsdkpath ", "Path to the folder containing ILCompiler application dependencies.", CommandOptionType.SingleValue); + var ilcSdkPath = app.Option("--ilcsdkpath ", "Path to the folder containing custom built ILCompiler SDK.", CommandOptionType.SingleValue); + var appDepSdkPath = app.Option("--appdepsdkpath ", "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); app.OnExecute(() => @@ -64,6 +65,7 @@ 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(); @@ -78,7 +80,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); } } @@ -110,6 +112,7 @@ namespace Microsoft.DotNet.Tools.Compiler string ilcArgsValue, string ilcPathValue, string ilcSdkPathValue, + string appDepSdkPathValue, bool isCppMode) { var outputPath = GetOutputPath(context, configuration, outputOptionValue); @@ -146,10 +149,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) { From b3f43457bc1568f280308ae42995f52a893ca0ee Mon Sep 17 00:00:00 2001 From: Gaurav Khanna Date: Sat, 19 Dec 2015 15:19:57 -0800 Subject: [PATCH 16/21] Update CLI Readme.md for Mac JIT support --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cebfed7b8..a5d5158f6 100644 --- a/README.md +++ b/README.md @@ -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 From 61e9f2f2a569b03deb1af96189f707b171eab34d Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Fri, 18 Dec 2015 12:13:36 -0800 Subject: [PATCH 17/21] Adding a key to use for strong name signing of our assemblies and modifying the project.json of our projects to include that key. --- src/Microsoft.DotNet.Cli.Utils/project.json | 4 ++++ src/Microsoft.DotNet.Compiler.Common/project.json | 4 ++++ .../project.json | 3 +++ .../project.json | 3 +++ src/Microsoft.DotNet.ProjectModel/project.json | 3 +++ src/Microsoft.DotNet.Runtime/project.json | 2 +- .../project.json | 3 ++- .../project.json | 3 ++- tools/Key.snk | Bin 0 -> 596 bytes 9 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 tools/Key.snk diff --git a/src/Microsoft.DotNet.Cli.Utils/project.json b/src/Microsoft.DotNet.Cli.Utils/project.json index 7bafa034a..1a5670618 100644 --- a/src/Microsoft.DotNet.Cli.Utils/project.json +++ b/src/Microsoft.DotNet.Cli.Utils/project.json @@ -1,6 +1,10 @@ { "version": "1.0.0-*", + "compilationOptions": { + "keyFile": "../../tools/Key.snk" + }, + "shared": "**/*.cs", "dependencies": { diff --git a/src/Microsoft.DotNet.Compiler.Common/project.json b/src/Microsoft.DotNet.Compiler.Common/project.json index 3bd797244..33a18ddb1 100644 --- a/src/Microsoft.DotNet.Compiler.Common/project.json +++ b/src/Microsoft.DotNet.Compiler.Common/project.json @@ -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", diff --git a/src/Microsoft.DotNet.ProjectModel.Loader/project.json b/src/Microsoft.DotNet.ProjectModel.Loader/project.json index 8fb750e90..a2e50a291 100644 --- a/src/Microsoft.DotNet.ProjectModel.Loader/project.json +++ b/src/Microsoft.DotNet.ProjectModel.Loader/project.json @@ -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-*", diff --git a/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json b/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json index 408da528c..1b2b97dc8 100644 --- a/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json +++ b/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json @@ -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-*", diff --git a/src/Microsoft.DotNet.ProjectModel/project.json b/src/Microsoft.DotNet.ProjectModel/project.json index b9baad7e4..b3d58d43b 100644 --- a/src/Microsoft.DotNet.ProjectModel/project.json +++ b/src/Microsoft.DotNet.ProjectModel/project.json @@ -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", diff --git a/src/Microsoft.DotNet.Runtime/project.json b/src/Microsoft.DotNet.Runtime/project.json index dd5da7d01..26103a0f9 100644 --- a/src/Microsoft.DotNet.Runtime/project.json +++ b/src/Microsoft.DotNet.Runtime/project.json @@ -1,7 +1,7 @@ { "version": "1.0.0-*", "compilationOptions": { - "emitEntryPoint": true + "emitEntryPoint": true }, "dependencies": { diff --git a/src/Microsoft.Extensions.DependencyModel/project.json b/src/Microsoft.Extensions.DependencyModel/project.json index 1d2308e67..1cade77eb 100644 --- a/src/Microsoft.Extensions.DependencyModel/project.json +++ b/src/Microsoft.Extensions.DependencyModel/project.json @@ -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" diff --git a/src/Microsoft.Extensions.Testing.Abstractions/project.json b/src/Microsoft.Extensions.Testing.Abstractions/project.json index ac9342317..98036c680 100644 --- a/src/Microsoft.Extensions.Testing.Abstractions/project.json +++ b/src/Microsoft.Extensions.Testing.Abstractions/project.json @@ -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", diff --git a/tools/Key.snk b/tools/Key.snk new file mode 100644 index 0000000000000000000000000000000000000000..e10e4889c125d3120cd9e81582243d70f7cbb806 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098=Iw=HCsnz~#iVhm& zj%TU(_THUee?3yHBjk$37ysB?i5#7WD$={H zV4B!OxRPrb|8)HPg~A}8P>^=#y<)56#=E&NzcjOtPK~<4n6GHt=K$ro*T(lhby_@U zEk(hLzk1H)0yXj{A_5>fk-TgNoP|q6(tP2xo8zt8i%212CWM#AeCd?`hS|4~L({h~Moo(~vy&3Z z1uI}`fd^*>o=rwbAGymj6RM^pZm(*Kfhs+Y1#`-2JPWZMK8@;ZWCk2+9bX4YP);~fj-BU*R zQPvWv$89!{Rl9wM+zR>_TSkn^voYxA?2G iKnV#iZ6Ah`K>b=@=IjYJXrxL124zR(38)nxe+&q_$QXwJ literal 0 HcmV?d00001 From 155b5221af65c742a45638c5869094b73673edcc Mon Sep 17 00:00:00 2001 From: Gaurav Khanna Date: Sun, 20 Dec 2015 07:59:09 -0800 Subject: [PATCH 18/21] Fix bug to correctly redefine ILCSdkPath if ILCPath is overridden but ILCSDKPath is not. --- src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs index 913eb42d6..cb1ee9283 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs @@ -56,6 +56,12 @@ namespace Microsoft.DotNet.Tools.Compiler.Native if (!string.IsNullOrEmpty(IlcPath)) { config.IlcPath = IlcPath; + + // If ILCSdkPath is not specified, then default it to be the same as the overridden ILCPath + if (string.IsNullOrEmpty(IlcSdkPath)) + { + IlcSdkPath = IlcPath; + } } if (!string.IsNullOrEmpty(IlcSdkPath)) From 7c40de57bf969b88ce524c2537fe115fd02e42b4 Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Sun, 20 Dec 2015 11:32:19 -0800 Subject: [PATCH 19/21] Removing strong name signing from utils, as it is not compiled --- src/Microsoft.DotNet.Cli.Utils/project.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Microsoft.DotNet.Cli.Utils/project.json b/src/Microsoft.DotNet.Cli.Utils/project.json index 1a5670618..7bafa034a 100644 --- a/src/Microsoft.DotNet.Cli.Utils/project.json +++ b/src/Microsoft.DotNet.Cli.Utils/project.json @@ -1,10 +1,6 @@ { "version": "1.0.0-*", - "compilationOptions": { - "keyFile": "../../tools/Key.snk" - }, - "shared": "**/*.cs", "dependencies": { From 504e109947865d98bb87b1dec4c7d8f5daf0f8fe Mon Sep 17 00:00:00 2001 From: Senthil Date: Thu, 10 Dec 2015 19:06:55 -0800 Subject: [PATCH 20/21] Redesign corehost per spec --- src/corehost/CMakeLists.txt | 6 +- src/corehost/inc/args.h | 8 +- src/corehost/inc/deps_resolver.h | 102 +++++ src/corehost/inc/pal.h | 34 +- src/corehost/inc/servicing_index.h | 24 ++ src/corehost/inc/tpafile.h | 41 -- src/corehost/inc/utils.h | 4 +- src/corehost/src/args.cpp | 69 ++-- src/corehost/src/coreclr.cpp | 4 +- src/corehost/src/deps_resolver.cpp | 597 +++++++++++++++++++++++++++ src/corehost/src/main.cpp | 261 ++++++------ src/corehost/src/pal.unix.cpp | 58 +-- src/corehost/src/pal.windows.cpp | 77 ++-- src/corehost/src/servicing_index.cpp | 128 ++++++ src/corehost/src/tpafile.cpp | 251 ----------- src/corehost/src/utils.cpp | 21 +- 16 files changed, 1159 insertions(+), 526 deletions(-) create mode 100644 src/corehost/inc/deps_resolver.h create mode 100644 src/corehost/inc/servicing_index.h delete mode 100644 src/corehost/inc/tpafile.h create mode 100644 src/corehost/src/deps_resolver.cpp create mode 100644 src/corehost/src/servicing_index.cpp delete mode 100644 src/corehost/src/tpafile.cpp diff --git a/src/corehost/CMakeLists.txt b/src/corehost/CMakeLists.txt index 91bd5e945..8f9657fe1 100644 --- a/src/corehost/CMakeLists.txt +++ b/src/corehost/CMakeLists.txt @@ -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) diff --git a/src/corehost/inc/args.h b/src/corehost/inc/args.h index 66ff90f79..c5a51ac81 100644 --- a/src/corehost/inc/args.h +++ b/src/corehost/inc/args.h @@ -4,14 +4,20 @@ #ifndef ARGS_H #define ARGS_H +#include "utils.h" #include "pal.h" #include "trace.h" struct arguments_t { pal::string_t own_path; + pal::string_t app_dir; + pal::string_t dotnet_servicing; + pal::string_t dotnet_runtime_servicing; + pal::string_t dotnet_home; + pal::string_t dotnet_packages; + pal::string_t dotnet_packages_cache; pal::string_t managed_application; - pal::string_t clr_path; int app_argc; const pal::char_t** app_argv; diff --git a/src/corehost/inc/deps_resolver.h b/src/corehost/inc/deps_resolver.h new file mode 100644 index 000000000..3bf3cd08c --- /dev/null +++ b/src/corehost/inc/deps_resolver.h @@ -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 + +#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 m_local_assemblies; + + // Entries in the dep file + std::vector 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 diff --git a/src/corehost/inc/pal.h b/src/corehost/inc/pal.h index 5332be836..6d6521045 100644 --- a/src/corehost/inc/pal.h +++ b/src/corehost/inc/pal.h @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #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 ifstream_t. + typedef std::basic_ifstream ifstream_t; + typedef std::istreambuf_iterator 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 ifstream_t; + typedef std::istreambuf_iterator 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 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* 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 diff --git a/src/corehost/inc/servicing_index.h b/src/corehost/inc/servicing_index.h new file mode 100644 index 000000000..6a188d458 --- /dev/null +++ b/src/corehost/inc/servicing_index.h @@ -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 m_redirections; + pal::string_t m_patch_root; + pal::string_t m_index_file; + bool m_parsed; +}; diff --git a/src/corehost/inc/tpafile.h b/src/corehost/inc/tpafile.h deleted file mode 100644 index 70ffcf472..000000000 --- a/src/corehost/inc/tpafile.h +++ /dev/null @@ -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 - -#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 m_entries; - std::vector m_native_search_paths; - std::vector m_package_search_paths; -}; - -#endif // TPAFILE_H diff --git a/src/corehost/inc/utils.h b/src/corehost/inc/utils.h index 8a9c6a022..8a0740932 100644 --- a/src/corehost/inc/utils.h +++ b/src/corehost/inc/utils.h @@ -10,7 +10,7 @@ bool ends_with(const pal::string_t& value, const pal::string_t& suffix); pal::string_t get_executable(const pal::string_t& filename); pal::string_t get_directory(const pal::string_t& path); pal::string_t get_filename(const pal::string_t& path); -void append_path(pal::string_t& path1, const pal::char_t* path2); +void append_path(pal::string_t* path1, const pal::char_t* path2); bool coreclr_exists_in_dir(const pal::string_t& candidate); - +void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl); #endif diff --git a/src/corehost/src/args.cpp b/src/corehost/src/args.cpp index 747a56ecc..f6094de4a 100644 --- a/src/corehost/src/args.cpp +++ b/src/corehost/src/args.cpp @@ -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; } diff --git a/src/corehost/src/coreclr.cpp b/src/corehost/src/coreclr.cpp index 3923ea64e..dc5a1e1d8 100644 --- a/src/corehost/src/coreclr.cpp +++ b/src/corehost/src/coreclr.cpp @@ -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; } diff --git a/src/corehost/src/deps_resolver.cpp b/src/corehost/src/deps_resolver.cpp new file mode 100644 index 000000000..fd176e4eb --- /dev/null +++ b/src/corehost/src/deps_resolver.cpp @@ -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 +#include +#include + +#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* 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* 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* 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 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 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 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 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 native = [] (const pal::string_t& str) { + return get_directory(str); + }; + std::function& action = (asset_type == _X("culture")) ? culture : native; + + std::set 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; +} diff --git a/src/corehost/src/main.cpp b/src/corehost/src/main.cpp index 5c9667502..62472e0b6 100644 --- a/src/corehost/src/main.cpp +++ b/src/corehost/src/main.cpp @@ -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 argv_strs(args.app_argc); + std::vector 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); } diff --git a/src/corehost/src/pal.unix.cpp b/src/corehost/src/pal.unix.cpp index 58e045040..bc7cb74a2 100644 --- a/src/corehost/src/pal.unix.cpp +++ b/src/corehost/src/pal.unix.cpp @@ -5,6 +5,7 @@ #include "utils.h" #include "trace.h" +#include #include #include #include @@ -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::readdir(const pal::string_t& path) +void pal::readdir(const pal::string_t& path, std::vector* list) { - std::vector files; + assert(list != nullptr); + + std::vector& files = *list; auto dir = opendir(path.c_str()); if (dir != nullptr) @@ -197,6 +205,4 @@ std::vector pal::readdir(const pal::string_t& path) files.push_back(pal::string_t(entry->d_name)); } } - - return files; } diff --git a/src/corehost/src/pal.windows.cpp b/src/corehost/src/pal.windows.cpp index 9059c940e..7ce29cc5c 100644 --- a/src/corehost/src/pal.windows.cpp +++ b/src/corehost/src/pal.windows.cpp @@ -5,23 +5,24 @@ #include "trace.h" #include "utils.h" +#include #include #include static std::wstring_convert, 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::readdir(const string_t& path) +void pal::readdir(const string_t& path, std::vector* list) { - std::vector files; + assert(list != nullptr); + + std::vector& files = *list; string_t search_string(path); search_string.push_back(DIR_SEPARATOR); @@ -174,6 +193,4 @@ std::vector pal::readdir(const string_t& path) files.push_back(filepath); } while (::FindNextFileW(handle, &data)); ::FindClose(handle); - - return files; } diff --git a/src/corehost/src/servicing_index.cpp b/src/corehost/src/servicing_index.cpp new file mode 100644 index 000000000..687006ff4 --- /dev/null +++ b/src/corehost/src/servicing_index.cpp @@ -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; +} diff --git a/src/corehost/src/tpafile.cpp b/src/corehost/src/tpafile.cpp deleted file mode 100644 index 092679a1a..000000000 --- a/src/corehost/src/tpafile.cpp +++ /dev/null @@ -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 -#include - -#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 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 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 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); -} diff --git a/src/corehost/src/utils.cpp b/src/corehost/src/utils.cpp index 222bb269d..bfb33ca1a 100644 --- a/src/corehost/src/utils.cpp +++ b/src/corehost/src/utils.cpp @@ -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; + } +} From 0b2a6fee2edee2a140387ed50dc7c4606c112ff4 Mon Sep 17 00:00:00 2001 From: Gaurav Khanna Date: Sun, 20 Dec 2015 20:46:28 -0800 Subject: [PATCH 21/21] Pickup headers from ILCompiler.SDK Add support for --cppcompilerflags switch to enable passing custom native compiler arguments. --- .../ArgValues.cs | 13 ++++++----- .../ArgumentsParser.cs | 7 +++++- .../Linux/LinuxCppCompileStep.cs | 21 ++++++++++------- .../Linux/LinuxRyuJitCompileStep.cs | 6 +++++ .../Mac/MacCppCompileStep.cs | 21 ++++++++++------- .../Mac/MacRyuJitCompileStep.cs | 11 +++++---- .../Windows/WindowsCppCompileStep.cs | 23 +++++++++++++------ .../Windows/WindowsLinkStep.cs | 4 +++- .../NativeCompileSettings.cs | 13 +++++++++++ .../appdep/project.json | 2 +- .../project.json | 4 ++-- .../Program.cs | 13 +++++++++-- test/E2E/E2ETest.cs | 6 ----- 13 files changed, 97 insertions(+), 47 deletions(-) diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs index cb1ee9283..4212fb0b9 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgValues.cs @@ -17,6 +17,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native 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; } @@ -56,12 +57,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native if (!string.IsNullOrEmpty(IlcPath)) { config.IlcPath = IlcPath; - - // If ILCSdkPath is not specified, then default it to be the same as the overridden ILCPath - if (string.IsNullOrEmpty(IlcSdkPath)) - { - IlcSdkPath = IlcPath; - } + config.IlcSdkPath = IlcPath; } if (!string.IsNullOrEmpty(IlcSdkPath)) @@ -79,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); diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs index f7d65588d..e471b0137 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/ArgumentsParser.cs @@ -23,6 +23,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native var help = false; string helpText = null; var returnCode = 0; + string cppCompilerFlags = null; IReadOnlyList references = Array.Empty(); IReadOnlyList linklib = Array.Empty(); @@ -57,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, @@ -131,7 +135,8 @@ namespace Microsoft.DotNet.Tools.Compiler.Native IlcSdkPath = ilcSdkPath, LinkLibPaths = linklib, AppDepSDKPath = appDepSdk, - LogPath = logPath + LogPath = logPath, + CppCompilerFlags = cppCompilerFlags }; } } diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs index 264ef6bc8..034f93a14 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs @@ -65,20 +65,25 @@ 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")); - + // Pass the optional native compiler flags if specified + if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags)) + { + argsList.Add(config.CppCompilerFlags); + } + // ILC SDK Libs foreach (var lib in IlcSdkLibs) { diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs index 74855c80b..de0d30179 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Linux/LinuxRyuJitCompileStep.cs @@ -70,6 +70,12 @@ namespace Microsoft.DotNet.Tools.Compiler.Native var inLibFile = DetermineInFile(config); argsList.Add(inLibFile); + // Pass the optional native compiler flags if specified + if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags)) + { + argsList.Add(config.CppCompilerFlags); + } + // ILC SDK Libs foreach (var lib in IlcSdkLibs) { diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs index a60a27d76..1a7bbeac5 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacCppCompileStep.cs @@ -67,23 +67,28 @@ 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); + // Pass the optional native compiler flags if specified + if (!string.IsNullOrWhiteSpace(config.CppCompilerFlags)) + { + argsList.Add(config.CppCompilerFlags); + } + // ILC SDK Libs foreach (var lib in IlcSdkLibs) { diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs index 7863e2b3f..985248486 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs @@ -66,11 +66,12 @@ 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); diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsCppCompileStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsCppCompileStep.cs index 30fa68d13..a6b0575c0 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsCppCompileStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsCppCompileStep.cs @@ -22,7 +22,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native private static readonly Dictionary ConfigurationCompilerOptionsMap = new Dictionary { - { 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}\""); diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs index ef3e03f92..8cb1ca93c 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/IntermediateCompilation/Windows/WindowsLinkStep.cs @@ -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 ConfigurationLinkLibMap = new Dictionary() { - { BuildConfiguration.debug , new string[] { "msvcrtd.lib" } }, + { BuildConfiguration.debug , new string[] { "msvcrt.lib" } }, { BuildConfiguration.release , new string[] { "msvcrt.lib" } } }; diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs b/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs index 268d5cc36..c4f192797 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/NativeCompileSettings.cs @@ -21,6 +21,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native private string _ilcArgs; private readonly List _referencePaths; private readonly List _linkLibPaths; + private string _cppCompilerFlags; public string LogPath { @@ -147,6 +148,18 @@ namespace Microsoft.DotNet.Tools.Compiler.Native } } + public string CppCompilerFlags + { + get + { + return _cppCompilerFlags; + } + set + { + _cppCompilerFlags = value; + } + } + private NativeCompileSettings() { _linkLibPaths = new List(); diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/appdep/project.json b/src/Microsoft.DotNet.Tools.Compiler.Native/appdep/project.json index 7f8eabbbf..77a897f5f 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/appdep/project.json +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/appdep/project.json @@ -5,7 +5,7 @@ "emitEntryPoint": true }, "dependencies": { - "Microsoft.DotNet.AppDep":"1.0.2-*" + "Microsoft.DotNet.AppDep":"1.0.3-*" }, "frameworks": { "dnxcore50": { } diff --git a/src/Microsoft.DotNet.Tools.Compiler.Native/project.json b/src/Microsoft.DotNet.Tools.Compiler.Native/project.json index 4506f707d..a4ca39223 100644 --- a/src/Microsoft.DotNet.Tools.Compiler.Native/project.json +++ b/src/Microsoft.DotNet.Tools.Compiler.Native/project.json @@ -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": { diff --git a/src/Microsoft.DotNet.Tools.Compiler/Program.cs b/src/Microsoft.DotNet.Tools.Compiler/Program.cs index 366f4765f..ceec01a1c 100644 --- a/src/Microsoft.DotNet.Tools.Compiler/Program.cs +++ b/src/Microsoft.DotNet.Tools.Compiler/Program.cs @@ -48,6 +48,7 @@ namespace Microsoft.DotNet.Tools.Compiler var ilcSdkPath = app.Option("--ilcsdkpath ", "Path to the folder containing custom built ILCompiler SDK.", CommandOptionType.SingleValue); var appDepSdkPath = app.Option("--appdepsdkpath ", "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 ", "Additional flags to be passed to the native compiler.", CommandOptionType.SingleValue); app.OnExecute(() => { @@ -69,6 +70,7 @@ namespace Microsoft.DotNet.Tools.Compiler 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; @@ -80,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, appDepSdkPathValue, isCppMode); + success &= CompileNative(context, configValue, outputValue, buildProjectReferences, intermediateValue, archValue, ilcArgsValue, ilcPathValue, ilcSdkPathValue, appDepSdkPathValue, isCppMode, cppCompilerFlagsValue); } } @@ -113,7 +115,8 @@ namespace Microsoft.DotNet.Tools.Compiler string ilcPathValue, string ilcSdkPathValue, string appDepSdkPathValue, - bool isCppMode) + bool isCppMode, + string cppCompilerFlagsValue) { var outputPath = GetOutputPath(context, configuration, outputOptionValue); var nativeOutputPath = Path.Combine(GetOutputPath(context, configuration, outputOptionValue), "native"); @@ -167,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) { diff --git a/test/E2E/E2ETest.cs b/test/E2E/E2ETest.cs index d35e8698a..0165f63d0 100644 --- a/test/E2E/E2ETest.cs +++ b/test/E2E/E2ETest.cs @@ -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}");